diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/011_passage_uncertainty_evaluation_files/figure-html/cell-26-output-1.png b/011_passage_uncertainty_evaluation_files/figure-html/cell-26-output-1.png new file mode 100644 index 0000000..a2ed6f1 Binary files /dev/null and b/011_passage_uncertainty_evaluation_files/figure-html/cell-26-output-1.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..320be7c --- /dev/null +++ b/index.html @@ -0,0 +1,659 @@ + + + + + + + + + + +Vespa Data Science - Vespa for Data Scientists + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Vespa for Data Scientists

+
+ +
+
+ Library and documentation to support Vespa data science use cases. +
+
+ + +
+ + + + +
+ + +
+ + +

+
+

Motivation

+

This library contains application specific code related to data manipulation and analysis of different Vespa use cases. The Vespa python API is used to interact with Vespa applications from python for faster exploration.

+

The main goal of this space is to facilitate prototyping and experimentation for data scientists. Please visit Vespa sample apps for production-ready use cases and Vespa docs for in-depth Vespa documentation.

+
+
+

Install

+

Code to support and reproduce the use cases documented here can be found in the learntorank library.

+

Install via PyPI:

+

pip install learntorank

+
+
+

Development

+

All the code and content of this repo is created using nbdev by editing notebooks. We will give a summary below about the main points required to contribute, but we suggest going through nbdev tutorials to learn more.

+
+

Setting up environment

+
    +
  1. Create and activate a virtual environment of your choice. We recommend pipenv.

    +
    pipenv shell
  2. +
  3. Install Jupyter Lab (or Jupyter Notebook if you prefer).

    +
    pip3 install jupyterlab
  4. +
  5. Create a new kernel for Jupyter that uses the virtual environment created at step 1.

    +
      +
    • Check where the current list of kernels is located with jupyter kernelspec list.
    • +
    • Copy one of the existing folder and rename it to learntorank.
    • +
    • Modify the kernel.json file that is inside the new folder to reflect the python3executable associated with your virtual env.
    • +
  6. +
  7. Install nbdev library:

    +
    pip3 install nbdev
  8. +
  9. Install learntorank in development mode:

    +
    pip3 install -e .[dev]
  10. +
+
+
+

Most used nbdev commands

+

From your terminal:

+
    +
  • nbdev_help: List all nbdev commands available.

  • +
  • nbdev_readme: Update README.md based on index.ipynb

  • +
  • Preview documentation while editing the notebooks:

    +
      +
    • nbdev_preview --port 3000
    • +
  • +
  • Workflow before pushing code:

    +
      +
    • nbdev_test --n_workers 2: Execute all the tests inside notebooks. +
        +
      • Tests can run in parallel but since we create Docker containers we suggest a low number of workers to preserve memory.
      • +
    • +
    • nbdev_export: Export code from notebooks to the python library.
    • +
    • nbdev_clean: Clean notebooks to avoid merge conflicts.
    • +
  • +
  • Publish library

    +
      +
    • nbdev_bump_version: Bump library version.
    • +
    • nbdev_pypi: Publish library to PyPI.
    • +
  • +
+ + +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/module_evaluation.html b/module_evaluation.html new file mode 100644 index 0000000..650efd9 --- /dev/null +++ b/module_evaluation.html @@ -0,0 +1,2053 @@ + + + + + + + + + + +Vespa Data Science - evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

evaluation

+
+ +
+
+ Reference API related to evaluation function and metrics +
+
+ + +
+ + + + +
+ + +
+ + +
+

Metrics

+

Abstract and concrete classes related to evaluation metrics.

+
+

source

+
+

EvalMetric

+
+
 EvalMetric ()
+
+

Abstract class for evaluation metric.

+
+

source

+
+
+

EvalMetric.evaluate_query

+
+
 EvalMetric.evaluate_query (query_results, relevant_docs, id_field,
+                            default_score, detailed_metrics=False)
+
+

Abstract method to be implemented by metrics inheriting from EvalMetric to evaluate query results.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
query_resultsRaw query results returned by Vespa.
relevant_docsEach dict contains a doc id a optionally a doc score.
id_fieldThe Vespa field representing the document id.
default_scoreScore to assign to the additional documents that are not relevant. Default to 0.
detailed_metricsboolFalseReturn intermediate computations if available.
Returnstyping.DictMetric values.
+
+

source

+
+
+

MatchRatio

+
+
 MatchRatio ()
+
+

Computes the ratio of documents retrieved by the match phase.

+

Instantiate the metric:

+
+
metric = MatchRatio()
+
+
+

source

+
+
+

MatchRatio.evaluate_query

+
+
 MatchRatio.evaluate_query (query_results:vespa.io.VespaQueryResponse,
+                            relevant_docs:List[Dict], id_field:str,
+                            default_score:int, detailed_metrics=False)
+
+

Evaluate query results according to match ratio metric.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
query_resultsVespaQueryResponseRaw query results returned by Vespa.
relevant_docstyping.List[typing.Dict]Each dict contains a doc id a optionally a doc score.
id_fieldstrThe Vespa field representing the document id.
default_scoreintScore to assign to the additional documents that are not relevant. Default to 0.
detailed_metricsboolFalseReturn intermediate computations if available.
Returnstyping.DictReturns the match ratio. In addition, if detailed_metrics=False, returns the number of retrieved docs _retrieved_docs and the number of docs available in the corpus _docs_available.
+

Compute match ratio:

+
+
evaluation = metric.evaluate_query(
+    query_results=query_results, 
+    relevant_docs=None,
+    id_field="vespa_id_field",
+    default_score=0,
+)
+evaluation
+
+
{'match_ratio': 0.01731996353691887}
+
+
+

Return detailed metrics, in addition to match ratio:

+
+
evaluation = metric.evaluate_query(
+    query_results=query_results,
+    relevant_docs=None,
+    id_field="vespa_id_field",
+    default_score=0,
+    detailed_metrics=True,
+)
+evaluation
+
+
{'match_ratio': 0.01731996353691887,
+ 'match_ratio_retrieved_docs': 1083,
+ 'match_ratio_docs_available': 62529}
+
+
+
+

source

+
+
+

TimeQuery

+
+
 TimeQuery ()
+
+

Compute the time it takes for Vespa to execute the query..

+

Instantiate the metric:

+
+
time_metric = TimeQuery()
+
+
+

source

+
+
+

TimeQuery.evaluate_query

+
+
 TimeQuery.evaluate_query (query_results:vespa.io.VespaQueryResponse,
+                           relevant_docs:List[Dict], id_field:str,
+                           default_score:int, detailed_metrics=False)
+
+

Evaluate query results according to query time metric.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
query_resultsVespaQueryResponseRaw query results returned by Vespa.
relevant_docstyping.List[typing.Dict]Each dict contains a doc id a optionally a doc score.
id_fieldstrThe Vespa field representing the document id.
default_scoreintScore to assign to the additional documents that are not relevant. Default to 0.
detailed_metricsboolFalseReturn intermediate computations if available.
Returnstyping.DictReturns the match ratio. In addition, if detailed_metrics=False, returns the number of retrieved docs _retrieved_docs and the number of docs available in the corpus _docs_available.
+

Compute the query time a client would observe (except network latency).

+
+
time_metric.evaluate_query(
+    query_results=query_results, 
+    relevant_docs=None, 
+    id_field="vespa_id_field",
+    default_score=0
+)
+
+
{'search_time': 0.013}
+
+
+

Include detailed metrics. In addition to the search_time above, it returns the time to execute the first protocol phase/matching phase (search_time_query_time) and the time to execute the summary fill protocol phase for the globally ordered top-k hits (search_time_summary_fetch_time).

+
+
time_metric.evaluate_query(
+    query_results=query_results, 
+    relevant_docs=None, 
+    id_field="vespa_id_field",
+    default_score=0,
+    detailed_metrics=True
+)
+
+
{'search_time': 0.013,
+ 'search_time_query_time': 0.01,
+ 'search_time_summary_fetch_time': 0.002}
+
+
+
+

source

+
+
+

Recall

+
+
 Recall (at:int)
+
+

Compute the recall at position at.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
atintMaximum position on the resulting list to look for relevant docs.
ReturnsNone
+

Instantiate the metric:

+
+
recall_1 = Recall(at=1)
+recall_2 = Recall(at=2)
+recall_3 = Recall(at=3)
+
+
+

source

+
+
+

Recall.evaluate_query

+
+
 Recall.evaluate_query (query_results:vespa.io.VespaQueryResponse,
+                        relevant_docs:List[Dict], id_field:str,
+                        default_score:int, detailed_metrics=False)
+
+

Evaluate query results according to recall metric.

+

There is an assumption that only documents with score > 0 are relevant. Recall is equal to zero in case no relevant documents with score > 0 is provided.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
query_resultsVespaQueryResponseRaw query results returned by Vespa.
relevant_docstyping.List[typing.Dict]Each dict contains a doc id a optionally a doc score.
id_fieldstrThe Vespa field representing the document id.
default_scoreintScore to assign to the additional documents that are not relevant. Default to 0.
detailed_metricsboolFalseReturn intermediate computations if available.
Returnstyping.DictReturns the recall value.
+

Compute recall:

+
+
evaluation = recall_2.evaluate_query(
+    query_results=query_results,
+    relevant_docs=relevant_docs,
+    id_field="vespa_id_field",
+    default_score=0,
+)
+evaluation
+
+
{'recall_2': 0.5}
+
+
+

Compute recall:

+
+

source

+
+
+

ReciprocalRank

+
+
 ReciprocalRank (at:int)
+
+

Compute the reciprocal rank at position at

+ +++++ + + + + + + + + + + + + + + +
TypeDetails
atintMaximum position on the resulting list to look for relevant docs.
+

Instantiate the metric:

+
+
rr_1 = ReciprocalRank(at=1)
+rr_2 = ReciprocalRank(at=2)
+rr_3 = ReciprocalRank(at=3)
+
+
+

source

+
+
+

ReciprocalRank.evaluate_query

+
+
 ReciprocalRank.evaluate_query (query_results:vespa.io.VespaQueryResponse,
+                                relevant_docs:List[Dict], id_field:str,
+                                default_score:int, detailed_metrics=False)
+
+

Evaluate query results according to reciprocal rank metric.

+

There is an assumption that only documents with score > 0 are relevant.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
query_resultsVespaQueryResponseRaw query results returned by Vespa.
relevant_docstyping.List[typing.Dict]Each dict contains a doc id a optionally a doc score.
id_fieldstrThe Vespa field representing the document id.
default_scoreintScore to assign to the additional documents that are not relevant. Default to 0.
detailed_metricsboolFalseReturn intermediate computations if available.
Returnstyping.DictReturns the reciprocal rank value.
+

Compute reciprocal rank:

+
+
evaluation = rr_2.evaluate_query(
+    query_results=query_results,
+    relevant_docs=relevant_docs,
+    id_field="vespa_id_field",
+    default_score=0,
+)
+evaluation
+
+
{'reciprocal_rank_2': 0.5}
+
+
+
+

source

+
+
+

NormalizedDiscountedCumulativeGain

+
+
 NormalizedDiscountedCumulativeGain (at:int)
+
+

Compute the normalized discounted cumulative gain at position at.

+ +++++ + + + + + + + + + + + + + + +
TypeDetails
atintMaximum position on the resulting list to look for relevant docs.
+

Instantiate the metric:

+
+
ndcg_1 = NormalizedDiscountedCumulativeGain(at=1)
+ndcg_2 = NormalizedDiscountedCumulativeGain(at=2)
+ndcg_3 = NormalizedDiscountedCumulativeGain(at=3)
+
+
+

source

+
+
+

NormalizedDiscountedCumulativeGain.evaluate_query

+
+
 NormalizedDiscountedCumulativeGain.evaluate_query
+                                                    (query_results:vespa.i
+                                                    o.VespaQueryResponse, 
+                                                    relevant_docs:List[Dic
+                                                    t], id_field:str,
+                                                    default_score:int, det
+                                                    ailed_metrics=False)
+
+

Evaluate query results according to normalized discounted cumulative gain.

+

There is an assumption that documents returned by the query that are not included in the set of relevant documents have score equal to zero. Similarly, if the query returns a number N < at documents, we will assume that those N - at missing scores are equal to zero.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
query_resultsVespaQueryResponseRaw query results returned by Vespa.
relevant_docstyping.List[typing.Dict]Each dict contains a doc id a optionally a doc score.
id_fieldstrThe Vespa field representing the document id.
default_scoreintScore to assign to the additional documents that are not relevant. Default to 0.
detailed_metricsboolFalseReturn intermediate computations if available.
Returnstyping.DictReturns the normalized discounted cumulative gain. In addition, if detailed_metrics=False, returns the ideal discounted cumulative gain _ideal_dcg, the discounted cumulative gain _dcg.
+

Compute NDCG:

+
+
metric = NormalizedDiscountedCumulativeGain(at=2)
+evaluation = ndcg_2.evaluate_query(
+    query_results=query_results,
+    relevant_docs=relevant_docs,
+    id_field="vespa_id_field",
+    default_score=0,
+)
+evaluation
+
+
{'ndcg_2': 0.38685280723454163}
+
+
+

Return detailed metrics, in addition to NDCG:

+
+
evaluation = ndcg_2.evaluate_query(
+    query_results=query_results,
+    relevant_docs=relevant_docs,
+    id_field="vespa_id_field",
+    default_score=0,
+    detailed_metrics=True,
+)
+evaluation
+
+
{'ndcg_2': 0.38685280723454163,
+ 'ndcg_2_ideal_dcg': 1.6309297535714575,
+ 'ndcg_2_dcg': 0.6309297535714575}
+
+
+
+
+
+

Evaluation queries in batch

+
+

source

+
+

evaluate

+
+
 evaluate (app:vespa.application.Vespa,
+           labeled_data:Union[List[Dict],pandas.core.frame.DataFrame],
+           eval_metrics:List[__main__.EvalMetric], query_model:Union[learn
+           torank.query.QueryModel,List[learntorank.query.QueryModel]],
+           id_field:str, default_score:int=0, detailed_metrics=False,
+           per_query=False, aggregators=None, timeout=1000, **kwargs)
+
+

Evaluate a QueryModel according to a list of EvalMetric.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appVespaConnection to a Vespa application.
labeled_datatyping.Union[typing.List[typing.Dict], pandas.core.frame.DataFrame]Data containing query, query_id and relevant docs. See examples below for format.
eval_metricstyping.List[main.EvalMetric]Evaluation metrics
query_modeltyping.Union[learntorank.query.QueryModel, typing.List[learntorank.query.QueryModel]]Query models to be evaluated
id_fieldstrThe Vespa field representing the document id.
default_scoreint0Score to assign to the additional documents that are not relevant.
detailed_metricsboolFalseReturn intermediate computations if available.
per_queryboolFalseSet to True to return evaluation metrics per query.
aggregatorsNoneTypeNoneUsed only if per_query=False. List of pandas friendly aggregators to summarize per model metrics. We use [“mean”, “median”, “std”] by default.
timeoutint1000Vespa query timeout in ms.
kwargs
ReturnsDataFrameReturns query_id and metrics according to the selected evaluation metrics.
+

Usage:

+

Setup and feed a Vespa application:

+
+
from learntorank.passage import create_basic_search_package
+from learntorank.passage import PassageData
+from vespa.deployment import VespaDocker
+
+
+
app_package = create_basic_search_package(name="EvaluationApp")
+vespa_docker = VespaDocker(port=8082, cfgsrv_port=19072)
+app = vespa_docker.deploy(application_package=app_package)
+data = PassageData.load()
+responses = app.feed_df(
+    df=data.get_corpus(), 
+    include_id=True, 
+    id_field="doc_id"
+)
+
+

Define query models to be evaluated:

+
+
from learntorank.query import OR, Ranking
+
+
+
bm25_query_model = QueryModel(
+    name="bm25", 
+    match_phase=OR(), 
+    ranking=Ranking(name="bm25")
+)
+native_query_model = QueryModel(
+    name="native_rank", 
+    match_phase=OR(), 
+    ranking=Ranking(name="native_rank")
+)
+
+

Define metrics to compute during evaluation:

+
+
metrics = [
+    Recall(at=10), 
+    ReciprocalRank(at=3), 
+    NormalizedDiscountedCumulativeGain(at=3)
+]
+
+

Get labeled data:

+
+
labeled_data = data.get_labels(type="dev")
+labeled_data[0:2]
+
+
[{'query_id': '1101971',
+  'query': 'why say the sky is the limit',
+  'relevant_docs': [{'id': '7407715', 'score': 1}]},
+ {'query_id': '712898',
+  'query': 'what is an cvc in radiology',
+  'relevant_docs': [{'id': '7661336', 'score': 1}]}]
+
+
+

Evaluate:

+
+
evaluation = evaluate(
+    app=app,
+    labeled_data=labeled_data, 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+)
+evaluation
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelbm25native_rank
recall_10mean0.9358330.845833
median1.0000001.000000
std0.2154440.342749
reciprocal_rank_3mean0.9350000.746667
median1.0000001.000000
std0.2319770.399551
ndcg_3mean0.9128390.740814
median1.0000001.000000
std0.2422720.387611
+ +
+
+
+

The evaluate function also accepts labeled data as a data frame:

+
+
labeled_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
qidquerydoc_idrelevance
01101971why say the sky is the limit74077151
1712898what is an cvc in radiology76613361
2154469dmv california how long does it take to get id79145441
3930015what's an epigraph79287051
4860085what is va tax29153831
+ +
+
+
+
+
evaluation_df = evaluate(
+    app=app,
+    labeled_data=labeled_df, 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+)
+evaluation_df
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelbm25native_rank
recall_10mean0.9358330.845833
median1.0000001.000000
std0.2154440.342749
reciprocal_rank_3mean0.9350000.746667
median1.0000001.000000
std0.2319770.399551
ndcg_3mean0.9128390.740814
median1.0000001.000000
std0.2422720.387611
+ +
+
+
+

Control which aggregators are computed:

+
+
evaluation = evaluate(
+    app=app,
+    labeled_data=labeled_data, 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+    aggregators=["mean", "std"]
+)
+evaluation
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelbm25native_rank
recall_10mean0.9358330.845833
std0.2154440.342749
reciprocal_rank_3mean0.9350000.746667
std0.2319770.399551
ndcg_3mean0.9128390.740814
std0.2422720.387611
+ +
+
+
+

Include detailed metrics when available, this includes intermediate steps that are available for some of the metrics:

+
+
evaluation = evaluate(
+    app=app,
+    labeled_data=labeled_data, 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+    aggregators=["mean", "std"],
+    detailed_metrics=True
+)
+evaluation
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelbm25native_rank
recall_10mean0.9358330.845833
std0.2154440.342749
reciprocal_rank_3mean0.9350000.746667
std0.2319770.399551
ndcg_3mean0.9128390.740814
std0.2422720.387611
ndcg_3_ideal_dcgmean1.0541651.054165
std0.2073150.207315
ndcg_3_dcgmean0.9389280.765474
std0.2255330.387161
+ +
+
+
+

Generate results per query:

+
+
evaluation = evaluate(
+    app=app,
+    labeled_data=labeled_data, 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+    per_query=True
+)
+evaluation.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelquery_idrecall_10reciprocal_rank_3ndcg_3
0native_rank11019711.01.01.0
1native_rank7128980.00.00.0
2native_rank1544691.00.00.0
3native_rank9300151.00.00.0
4native_rank8600850.00.00.0
+ +
+
+
+
+
+
+

Evaluate specific query

+
+

source

+
+

evaluate_query

+
+
 evaluate_query (app:vespa.application.Vespa,
+                 eval_metrics:List[__main__.EvalMetric],
+                 query_model:learntorank.query.QueryModel, query_id:str,
+                 query:str, id_field:str, relevant_docs:List[Dict],
+                 default_score:int=0, detailed_metrics=False, **kwargs)
+
+

Evaluate a single query according to evaluation metrics

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appVespaConnection to a Vespa application.
eval_metricstyping.List[main.EvalMetric]Evaluation metrics
query_modelQueryModelQuery model to be evaluated
query_idstrQuery id represented as str.
querystrQuery string.
id_fieldstrThe Vespa field representing the document id.
relevant_docstyping.List[typing.Dict]Each dict contains a doc id a optionally a doc score.
default_scoreint0Score to assign to the additional documents that are not relevant.
detailed_metricsboolFalseReturn intermediate computations if available.
kwargs
Returnstyping.DictContains query_id and metrics according to the selected evaluation metrics.
+

Usage:

+
+
app = Vespa(url = "https://api.cord19.vespa.ai")
+query_model = QueryModel(
+    match_phase = OR(),
+    ranking = Ranking(name="bm25", list_features=True))
+
+

Evaluate a single query:

+
+
query_evaluation = evaluate_query(
+    app=app,
+    eval_metrics = eval_metrics, 
+    query_model = bm25_query_model, 
+    query_id = "0", 
+    query = "Intrauterine virus infections and congenital heart disease", 
+    id_field = "id",
+    relevant_docs = [{"id": 0, "score": 1}, {"id": 3, "score": 1}],
+    default_score = 0
+)
+query_evaluation
+
+
{'model': 'bm25',
+ 'query_id': '0',
+ 'match_ratio': 0.814424921006077,
+ 'recall_10': 0.0,
+ 'reciprocal_rank_10': 0}
+
+
+
+
+
+

Evaluate query under specific document ids

+

Use recall to specify which documents should be included in the evaluation.

+

In the example below, we include documents with id equal to 0, 1 and 2. Since the relevant documents for this query are the documents with id 0 and 3, we should get recall equal to 0.5.

+
+
query_evaluation = evaluate_query(
+    app=app,
+    eval_metrics = eval_metrics, 
+    query_model = query_model, 
+    query_id = 0, 
+    query = "Intrauterine virus infections and congenital heart disease", 
+    id_field = "id",
+    relevant_docs = [{"id": 0, "score": 1}, {"id": 3, "score": 1}],
+    default_score = 0,
+    recall = ("id", [0, 1, 2])
+)
+query_evaluation
+
+
{'model': 'default_name',
+ 'query_id': 0,
+ 'match_ratio': 9.70242657688688e-06,
+ 'recall_10': 0.5,
+ 'reciprocal_rank_10': 1.0}
+
+
+

We now include documents with id equal to 0, 1, 2 and 3. This should give a recall equal to 1.

+
+
query_evaluation = evaluate_query(
+    app=app,
+    eval_metrics = eval_metrics, 
+    query_model = query_model, 
+    query_id = 0, 
+    query = "Intrauterine virus infections and congenital heart disease", 
+    id_field = "id",
+    relevant_docs = [{"id": 0, "score": 1}, {"id": 3, "score": 1}],
+    default_score = 0,
+    recall = ("id", [0, 1, 2, 3])
+)
+query_evaluation
+
+
{'model': 'default_name',
+ 'query_id': 0,
+ 'match_ratio': 1.2936568769182506e-05,
+ 'recall_10': 1.0,
+ 'reciprocal_rank_10': 1.0}
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/module_ml.html b/module_ml.html new file mode 100644 index 0000000..e8326fe --- /dev/null +++ b/module_ml.html @@ -0,0 +1,1331 @@ + + + + + + + + + + +Vespa Data Science - ml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

ml

+
+ +
+
+ Reference API related to ml applications +
+
+ + +
+ + + + +
+ + +
+ + +
+

ML tasks involving text inputs

+
+

source

+
+

Task

+
+
 Task (model_id:str)
+
+

Base class for ML Tasks.

+ +++++ + + + + + + + + + + + + + + +
TypeDetails
model_idstrId used to identify the model on Vespa applications.
+
+

source

+
+
+

TextTask

+
+
 TextTask (model_id:str, model:str, tokenizer:Optional[str]=None,
+           output_file:<class'IO'>=<_io.StringIO object at
+           0x7f8fe5e67160>)
+
+

Base class for Tasks involving text inputs.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
model_idstrId used to identify the model on Vespa applications.
modelstrId of the model as used by the model hub.
tokenizertyping.Optional[str]NoneId of the tokenizer as used by the model hub.
output_fileIO<_io.StringIO object at 0x7f8fe5e67160>Output file to write output messages.
+
+

source

+
+
+

TextTask.export_to_onnx

+
+
 TextTask.export_to_onnx (output_path:str)
+
+

Export a model to ONNX

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
output_pathstrRelative output path for the onnx model, should end in ‘.onnx’
ReturnsNone
+
+

source

+
+
+

TextTask.predict

+
+
 TextTask.predict (text:str)
+
+

Predict using a local instance of the model

+ + + + + + + + + + + + + + + + + + + + +
TypeDetails
textstrtext input for the task.
Returnstyping.ListPredictions.
+
+

source

+
+
+

SequenceClassification

+
+
 SequenceClassification (model_id:str, model:str,
+                         tokenizer:Optional[str]=None,
+                         output_file:<class'IO'>=<_io.StringIO object at
+                         0x7f8f7b2e3b80>)
+
+

Sequence Classification task.

+

It takes a text input and returns an array of floats depending on which model is used to solve the task.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
model_idstrId used to identify the model on Vespa applications.
modelstrId of the model as used by the model hub. Alternatively, it can also be the path to the folder containing the model files, as long as the model config is also there.
tokenizertyping.Optional[str]NoneId of the tokenizer as used by the model hub. Alternatively, it can also be the path to the folder containing the tokenizer files, as long as the model config is also there.
output_fileIO<_io.StringIO object at 0x7f8f7b2e3b80>Output file to write output messages.
+
+
+
+

Model config for Vespa applications

+
+

source

+
+

ModelConfig

+
+
 ModelConfig (model_id)
+
+

Base model configuration for Vespa applications.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
model_idUnique model id to represent the model within a Vespa application.
ReturnsNone
+
+

source

+
+
+

BertModelConfig

+
+
 BertModelConfig (model_id:str, query_input_size:int, doc_input_size:int,
+                  tokenizer:Union[str,os.PathLike],
+                  model:Union[str,os.PathLike,NoneType]=None)
+
+

BERT model configuration for Vespa applications.

+
+
+
+

bert_config = BertModelConfig( … model_id=“pretrained_bert_tiny”, … query_input_size=32, … doc_input_size=96, … tokenizer=“google/bert_uncased_L-2_H-128_A-2”, … model=“google/bert_uncased_L-2_H-128_A-2”, … ) # doctest: +SKIP BertModelConfig(‘pretrained_bert_tiny’, 32, 96, ‘google/bert_uncased_L-2_H-128_A-2’, ‘google/bert_uncased_L-2_H-128_A-2’)

+
+
+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
model_idstrUnique model id to represent the model within a Vespa application.
query_input_sizeintThe size of the input vector dedicated to the query text.
doc_input_sizeintThe size of the input vector dedicated to the document text.
tokenizertyping.Union[str, os.PathLike]The name or a path to a saved BERT model tokenizer from the transformers library.
modeltyping.Union[str, os.PathLike, NoneType]NoneThe name or a path to a saved model that is compatible with the tokenizer. The model is optional at construction since you might want to train it first. You must add a model via :func:add_model before deploying a Vespa application that uses this class.
ReturnsNone
+
+
#bert_config = BertModelConfig(
+#    model_id="pretrained_bert_tiny",
+#    query_input_size=32,
+#    doc_input_size=96,
+#    tokenizer="google/bert_uncased_L-2_H-128_A-2"
+#)
+
+
+
#bert_config = BertModelConfig(
+#    model_id="pretrained_bert_tiny",
+#    query_input_size=32,
+#    doc_input_size=96,
+#    tokenizer="google/bert_uncased_L-2_H-128_A-2",
+#    model="google/bert_uncased_L-2_H-128_A-2",
+#)
+
+
+

source

+
+
+

BertModelConfig.predict

+
+
 BertModelConfig.predict (queries, docs)
+
+

Predict (forward pass) given queries and docs texts

+ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
queriesA List of query texts.
docsA List of document texts.
Returnstyping.ListLogits
+
+

source

+
+
+

BertModelConfig.add_model

+
+
 BertModelConfig.add_model (model:Union[str,os.PathLike])
+
+

Add a BERT model

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
modeltyping.Union[str, os.PathLike]The name or a path to a saved model that is compatible with the tokenizer.
ReturnsNone
+
+

source

+
+
+

BertModelConfig.doc_fields

+
+
 BertModelConfig.doc_fields (text:str)
+
+

Generate document fields related to the model that needs to be fed to Vespa.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
textstrThe text related to the document to be used as input to the bert model
Returnstyping.DictDict with key and values as expected by Vespa.
+
+

source

+
+
+

BertModelConfig.query_tensor_mapping

+
+
 BertModelConfig.query_tensor_mapping (text:str)
+
+

Maps query text to a tensor expected by Vespa at run time.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
textstrQuery text to be used as input to the BERT model.
Returnstyping.List[float]Input ids expected by Vespa.
+
+

source

+
+
+

BertModelConfig.create_encodings

+
+
 BertModelConfig.create_encodings (queries:List[str], docs:List[str],
+                                   return_tensors=False)
+
+

Create BERT model encodings.

+

Create BERT encodings following the same pattern used during Vespa serving. Useful to generate training data and ensuring training and serving compatibility.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
queriestyping.List[str]Query texts.
docstyping.List[str]Document texts.
return_tensorsboolFalseReturn tensors
Returnstyping.DictDict containing input_ids, token_type_ids and attention_mask encodings.
+
+

source

+
+
+

BertModelConfig.export_to_onnx

+
+
 BertModelConfig.export_to_onnx (output_path:str)
+
+

Export a model to ONNX

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
output_pathstrRelative output path for the onnx model, should end in ‘.onnx’
ReturnsNone
+
+

source

+
+
+

BertModelConfig.onnx_model

+
+
 BertModelConfig.onnx_model ()
+
+
+

source

+
+
+

BertModelConfig.query_profile_type_fields

+
+
 BertModelConfig.query_profile_type_fields ()
+
+
+

source

+
+
+

BertModelConfig.document_fields

+
+
 BertModelConfig.document_fields (document_field_indexing)
+
+
+

source

+
+
+

BertModelConfig.rank_profile

+
+
 BertModelConfig.rank_profile (include_model_summary_features, **kwargs)
+
+
+
+
+

Model Server

+
+

source

+
+

ModelServer

+
+
 ModelServer (name:str, tasks:Optional[List[__main__.Task]]=None)
+
+

Create a Vespa stateless model evaluation server.

+

A Vespa stateless model evaluation server is a simplified Vespa application without content clusters.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
namestrApplication name.
taskstyping.Optional[typing.List[main.Task]]NoneList of tasks to be served.
+
+
+
+

Add ranking model

+
+

source

+
+

add_ranking_model

+
+
 add_ranking_model (app_package:vespa.package.ApplicationPackage,
+                    model_config:__main__.ModelConfig, schema=None,
+                    include_model_summary_features=False,
+                    document_field_indexing=None, **kwargs)
+
+

Add ranking profile based on a specific model config.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
app_packageApplicationPackageApplication package to include ranking model
model_configModelConfigModel config instance specifying the model to be used on the RankProfile.
schemaNoneTypeNoneName of the schema to add model ranking to.
include_model_summary_featuresboolFalseTrue to include model specific summary features, such as inputs and outputs that are useful for debugging. Default to False as this requires an extra model evaluation when fetching summary features.
document_field_indexingNoneTypeNoneList of indexing attributes for the document fields required by the ranking model.
kwargs
ReturnsNoneFurther arguments to be passed to RankProfile.
+ + +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/module_passage.html b/module_passage.html new file mode 100644 index 0000000..45a6ce4 --- /dev/null +++ b/module_passage.html @@ -0,0 +1,1479 @@ + + + + + + + + + + +Vespa Data Science - passage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

passage

+
+ +
+
+ Reference API related to the passage ranking use case. +
+
+ + +
+ + + + +
+ + +
+ + +
+

Data manipulation

+

Code related to the manipulation of passage ranking data.

+
+

source

+
+

sample_dict_items

+
+
 sample_dict_items (d:Dict, n:int)
+
+

Sample items from a dict.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
dtyping.Dictdict to be samples from.
nintNumber of samples
Returnstyping.Dictdict with sampled values
+

Usage:

+
+
d = {"a": 1, "b":2, "c":3}
+
+
+
sample_dict_items(d, 1)
+
+
{'a': 1}
+
+
+
+
sample_dict_items(d, 2)
+
+
{'c': 3, 'b': 2}
+
+
+
+
sample_dict_items(d, 3)
+
+
{'a': 1, 'c': 3, 'b': 2}
+
+
+

Return full dict in case number of samples is higher than length of the dict:

+
+
sample_dict_items(d, 4)
+
+
{'a': 1, 'c': 3, 'b': 2}
+
+
+
+

source

+
+
+

save_data

+
+
 save_data (corpus:Dict, train_qrels:Dict, train_queries:Dict,
+            dev_qrels:Dict, dev_queries:Dict,
+            file_path:str='passage_sample.json')
+
+

Save data to disk.

+

The main goal is to save sample data to disk.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
corpustyping.DictDocument corpus, see usage example below.
train_qrelstyping.DictTraining relevance scores, see usage example below.
train_queriestyping.DictTraining queries, see usage example below.
dev_qrelstyping.DictDevelopment relevance scores, see usage example below.
dev_queriestyping.DictDevelopment queries, see usage example below.
file_pathstrpassage_sample.jsonValid JSON file path.
ReturnsNoneSide-effect: data is saved to file_path.
+

Usage:

+
+
corpus = {
+    "0": "sentence 0", 
+    "1": "sentence 1", 
+    "2": "sentence 2", 
+    "3": "sentence 3"
+}
+train_queries = {
+    "10": "train query 10",
+    "11": "train query 11"
+}
+train_qrels = {
+    "10": {"0": 1},
+    "11": {"2": 1}
+}
+dev_queries = {
+    "20": "train query 20",
+    "21": "train query 21"
+}
+dev_qrels = {
+    "20": {"1": 1},
+    "21": {"3": 1}
+}
+
+
+
save_data(
+    corpus, 
+    train_qrels, train_queries, 
+    dev_qrels, dev_queries, 
+    file_path="passage_sample.json"
+)
+
+
+

source

+
+
+

load_data

+
+
 load_data (file_path:Optional[str]=None)
+
+

Load data.

+

The main goal is to load sample data from disk. If a file_path is not provided, a pre-generated data sample will be downloaded.

+ + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
file_pathtyping.Optional[str]Nonevalid JSON file path contain data saved by save_data. If None, a pre-generated sample will be downloaded.
Returnstyping.DictSee usage example below for expected format.
+

Usage:

+
    +
  • With file_path:
  • +
+
+
data = load_data("passage_sample.json")
+
+
+
data
+
+
{'corpus': {'0': 'sentence 0',
+  '1': 'sentence 1',
+  '2': 'sentence 2',
+  '3': 'sentence 3'},
+ 'train_qrels': {'10': {'0': 1}, '11': {'2': 1}},
+ 'train_queries': {'10': 'train query 10', '11': 'train query 11'},
+ 'dev_qrels': {'20': {'1': 1}, '21': {'3': 1}},
+ 'dev_queries': {'20': 'train query 20', '21': 'train query 21'}}
+
+
+
    +
  • Without file_path specified, a pre-generated sample data will be downloaded:
  • +
+
+
data = load_data()
+
+
+
data.keys()
+
+
dict_keys(['corpus', 'train_qrels', 'train_queries', 'dev_qrels', 'dev_queries'])
+
+
+
+
len(data["corpus"])
+
+
1000
+
+
+
+

source

+
+
+

PassageData

+
+
 PassageData (corpus:Optional[Dict]=None, train_qrels:Optional[Dict]=None,
+              train_queries:Optional[Dict]=None,
+              dev_qrels:Optional[Dict]=None,
+              dev_queries:Optional[Dict]=None)
+
+

Container for passage data

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
corpustyping.Optional[typing.Dict]NoneDocument corpus, see usage example below.
train_qrelstyping.Optional[typing.Dict]NoneTraining relevance scores, see usage example below.
train_queriestyping.Optional[typing.Dict]NoneTraining queries, see usage example below.
dev_qrelstyping.Optional[typing.Dict]NoneDevelopment relevance scores, see usage example below.
dev_queriestyping.Optional[typing.Dict]NoneDevelopment queries, see usage example below.
+

Usage:

+
+
corpus = {
+    "0": "sentence 0", 
+    "1": "sentence 1", 
+    "2": "sentence 2", 
+    "3": "sentence 3"
+}
+train_queries = {
+    "10": "train query 10",
+    "11": "train query 11"
+}
+train_qrels = {
+    "10": {"0": 1},
+    "11": {"2": 1}
+}
+dev_queries = {
+    "20": "train query 20",
+    "21": "train query 21"
+}
+dev_qrels = {
+    "20": {"1": 1},
+    "21": {"3": 1}
+}
+
+
+
passage_data = PassageData(
+    corpus=corpus, 
+    train_queries = train_queries, 
+    train_qrels=train_qrels,
+    dev_queries = dev_queries,
+    dev_qrels = dev_qrels
+)
+
+
+
passage_data
+
+
PassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)
+
+
+
+

source

+
+
+

PassageData.save

+
+
 PassageData.save (file_path:str='passage_sample.json')
+
+
+
passage_data.save()
+
+
+

source

+
+
+

PassageData.load

+
+
 PassageData.load (file_path:Optional[str]=None)
+
+

Load passage data from disk.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
file_pathtyping.Optional[str]Nonevalid JSON file path contain data saved by save_data. If None, a pre-generated sample will be downloaded.
ReturnsPassageData
+
+
data = PassageData.load(file_path="passage_sample.json")
+
+
+
data
+
+
PassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)
+
+
+
+

source

+
+
+

PassageData.summary

+
+
 PassageData.summary ()
+
+

Summary of the size of the dataset components.

+
+
data.summary
+
+
Number of documents: 4
+Number of train queries: 2
+Number of train relevance judgments: 2
+Number of dev queries: 2
+Number of dev relevance judgments: 2
+
+
+
+

source

+
+
+

PassageData.get_corpus

+
+
 PassageData.get_corpus ()
+
+
+
passage_data.get_corpus()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
doc_idtext
00sentence 0
11sentence 1
22sentence 2
33sentence 3
+ +
+
+
+
+

source

+
+
+

PassageData.get_queries

+
+
 PassageData.get_queries (type:str)
+
+

Get query data.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
typestrEither ‘train’ or ‘dev’.
ReturnsDataFrameDataFrame conaining ‘query_id’ and ‘query’.
+
+
passage_data.get_queries(type="train")
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
query_idquery
010train query 10
111train query 11
+ +
+
+
+
+
passage_data.get_queries(type="dev")
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
query_idquery
020train query 20
121train query 21
+ +
+
+
+
+

source

+
+
+

PassageData.get_labels

+
+
 PassageData.get_labels (type:str)
+
+

Get labeled data

+ + + + + + + + + + + + + + + + + + + + +
TypeDetails
typestrEither ‘train’ or ‘dev’.
Returnstyping.Dictpyvespa-formatted labeled data
+
+
passage_data.get_labels(type="train")
+
+
[{'query_id': '10',
+  'query': 'train query 10',
+  'relevant_docs': [{'id': '0', 'score': 1}]},
+ {'query_id': '11',
+  'query': 'train query 11',
+  'relevant_docs': [{'id': '2', 'score': 1}]}]
+
+
+
+
passage_data.get_labels(type="dev")
+
+
[{'query_id': '20',
+  'query': 'train query 20',
+  'relevant_docs': [{'id': '1', 'score': 1}]},
+ {'query_id': '21',
+  'query': 'train query 21',
+  'relevant_docs': [{'id': '3', 'score': 1}]}]
+
+
+
+

source

+
+
+

sample_data

+
+
 sample_data (n_relevant:int, n_irrelevant:int)
+
+

Sample data from the passage ranking dataset.

+

The final sample contains n_relevant train relevant documents, n_relevant dev relevant documents and n_irrelevant random documents sampled from the entire corpus.

+

All the relevant sampled documents, both from train and dev sets, are guaranteed to be on the corpus_sample, which will contain 2 * n_relevant + n_irrelevant documents.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
n_relevantintThe number of relevant documents to sample.
n_irrelevantintThe number of non-judged documents to sample.
ReturnsPassageData
+

Usage:

+
+
sample = sample_data(n_relevant=1, n_irrelevant=3)
+
+

The sampled corpus is a dict containing document id as key and the passage text as value.

+
+
sample.corpus
+
+
{'890370': 'the map of europe gives you a clear view of the political boundaries that segregate the countries in the continent including germany uk france spain italy greece romania ukraine hungary austria sweden finland norway czech republic belgium luxembourg switzerland croatia and albaniahe map of europe gives you a clear view of the political boundaries that segregate the countries in the continent including germany uk france spain italy greece romania ukraine hungary austria sweden finland norway czech republic belgium luxembourg switzerland croatia and albania',
+ '5060205': 'Setting custom HTTP headers with cURL can be done by using the CURLOPT_HTTPHEADER option, which can be set with the curl_setopt function. To add headers to your HTTP request you need to put them into a PHP Array, which you can then pass to the cul_setopt function, like demonstrated in the below example.',
+ '6096573': "The sugar in RNA is ribose, whereas the sugar in DNA is deoxyribose. The only difference between the two is that in deoxyribose, there is an oxygen missing from the 2' carbon …(there is a H there instead of an OH). This makes DNA more stable/less reactive than RNA. 1 person found this useful.",
+ '3092885': 'All three C-Ph bonds are typical of sp 3 - sp 2 carbon-carbon bonds with lengths of approximately 1.47 A, å while The-C o bond length is approximately.1 42. A å the presence of three adjacent phenyl groups confers special properties manifested in the reactivity of. the alcohol',
+ '7275560': 'shortest phase of mitosis Anaphase is the shortest phase of mitosis. During anaphase the arranged chromosomes at the metaphase plate are migrate towards their respective poles. Before this migration started, chromosomes are divided into sister chromatids, by the separation of joined centromere of two sister chromatids of a chromosomes.'}
+
+
+

The size of the sampled document corpus is equal to 2 * n_relevant + n_irrelevant.

+
+
len(sample.corpus)
+
+
5
+
+
+

Sampled queries are dict containing query id as key and query text as value.

+
+
print(sample.train_queries)
+print(sample.dev_queries)
+
+
{'899723': 'what sugar is found in rna'}
+{'994205': 'which is the shortest stage in duration'}
+
+
+

Sampled qrels contains one relevant document for each query.

+
+
print(sample.train_qrels)
+print(sample.dev_qrels)
+
+
{'899723': {'6096573': 1}}
+{'994205': {'7275560': 1}}
+
+
+

The following relevant documents are guaranteed to be included in the corpus_sample.

+
+
+
['6096573', '7275560']
+
+
+
+
+ +
+

Evaluate query models

+
+

source

+
+

evaluate_query_models

+
+
 evaluate_query_models (app_package:vespa.package.ApplicationPackage,
+                        query_models:List[learntorank.query.QueryModel],
+                        metrics:List[learntorank.evaluation.EvalMetric],
+                        corpus_size:List[int], output_file_path:str,
+                        dev_query_percentage:float=0.006285807802305023,
+                        verbose:bool=True, **kwargs)
+
+
+
from learntorank.evaluation import (
+    MatchRatio,
+    Recall, 
+    ReciprocalRank, 
+    NormalizedDiscountedCumulativeGain
+)
+from learntorank.query import QueryModel, OR, Ranking
+
+corpus_size = [100, 200]
+app_package = create_basic_search_package(name="PassageEvaluationApp")
+query_models = [
+    QueryModel(
+        name="bm25", 
+        match_phase=OR(), 
+        ranking=Ranking(name="bm25")
+    ),
+    QueryModel(
+        name="native_rank", 
+        match_phase=OR(), 
+        ranking=Ranking(name="native_rank")
+    )
+]
+metrics = [
+    MatchRatio(),
+    Recall(at=100), 
+    ReciprocalRank(at=10), 
+    NormalizedDiscountedCumulativeGain(at=10)
+]
+output_file_path = "test.csv"
+
+
+
estimates = evaluate_query_models(
+    app_package=app_package,
+    query_models=query_models,
+    metrics=metrics,
+    corpus_size=corpus_size,
+    dev_query_percentage=0.5,
+    output_file_path=output_file_path, 
+    verbose=False
+)
+
+
*****
+Deploy Vespa application:
+*****
+Waiting for configuration server, 0/300 seconds...
+Waiting for configuration server, 5/300 seconds...
+Waiting for configuration server, 10/300 seconds...
+Waiting for application status, 0/300 seconds...
+Waiting for application status, 5/300 seconds...
+Waiting for application status, 10/300 seconds...
+Waiting for application status, 15/300 seconds...
+Waiting for application status, 20/300 seconds...
+Waiting for application status, 25/300 seconds...
+Waiting for application status, 30/300 seconds...
+Waiting for application status, 35/300 seconds...
+Waiting for application status, 40/300 seconds...
+Waiting for application status, 45/300 seconds...
+Waiting for application status, 50/300 seconds...
+Waiting for application status, 55/300 seconds...
+Waiting for application status, 60/300 seconds...
+Waiting for application status, 65/300 seconds...
+Waiting for application status, 70/300 seconds...
+Waiting for application status, 75/300 seconds...
+Waiting for application status, 80/300 seconds...
+
+
+ + +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/module_query.html b/module_query.html new file mode 100644 index 0000000..1743be6 --- /dev/null +++ b/module_query.html @@ -0,0 +1,2168 @@ + + + + + + + + + + +Vespa Data Science - query + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

query

+
+ +
+
+ Reference API related to QueryModel related code +
+
+ + +
+ + + + +
+ + +
+ + +
+

Match Filters

+
+

source

+
+

MatchFilter

+
+
 MatchFilter ()
+
+

Abstract class for match filters.

+
+

source

+
+
+

AND

+
+
 AND ()
+
+

Filter that match document containing all the query terms.

+

Usage: The AND filter is usually used when specifying query models.

+
+
and_filter = AND()
+
+
+

source

+
+
+

OR

+
+
 OR ()
+
+

Filter that match any document containing at least one query term.

+

Usage: The OR filter is usually used when specifying query models.

+
+
or_filter = OR()
+
+
+

source

+
+
+

WeakAnd

+
+
 WeakAnd (hits:int, field:str='default')
+
+

Match documents according to the weakAND algorithm.

+

Reference: https://docs.vespa.ai/en/using-wand-with-vespa.html

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
hitsintLower bound on the number of hits to be retrieved.
fieldstrdefaultWhich Vespa field to search.
ReturnsNone
+

Usage: The WeakAnd filter is usually used when specifying query models.

+
+
weakand_filter = WeakAnd(hits=10, field="default")
+
+
+

source

+
+
+

Tokenize

+
+
 Tokenize (hits:int, field:str='default')
+
+

Match documents according to the weakAND algorithm without parsing specials characters.

+

Reference: https://docs.vespa.ai/en/reference/simple-query-language-reference.html

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
hitsintLower bound on the number of hits to be retrieved.
fieldstrdefaultWhich Vespa field to search.
ReturnsNone
+

Usage: The Tokenize filter is usually used when specifying query models.

+
+
tokenize_filter = Tokenize(hits=10, field="default")
+
+
+

source

+
+
+

ANN

+
+
 ANN (doc_vector:str, query_vector:str, hits:int, label:str,
+      approximate:bool=True)
+
+

Match documents according to the nearest neighbor operator.

+

Reference: https://docs.vespa.ai/en/reference/query-language-reference.html

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
doc_vectorstrName of the document field to be used in the distance calculation.
query_vectorstrName of the query field to be used in the distance calculation.
hitsintLower bound on the number of hits to return.
labelstrA label to identify this specific operator instance.
approximateboolTrueTrue to use approximate nearest neighbor and False to use brute force. Default to True.
ReturnsNone
+

Usage: The ANN filter is usually used when specifying query models.

+

By default, the ANN operator uses approximate nearest neighbor:

+
+
match_filter = ANN(
+    doc_vector="doc_vector",
+    query_vector="query_vector",
+    hits=10,
+    label="label",
+)
+
+

Brute-force can be used by specifying approximate=False:

+
+
ann_filter = ANN(
+    doc_vector="doc_vector",
+    query_vector="query_vector",
+    hits=10,
+    label="label",
+    approximate=False,
+)
+
+
+

source

+
+
+

Union

+
+
 Union (*args:__main__.MatchFilter)
+
+

Match documents that belongs to the union of many match filters.

+ + + + + + + + + + + + + + + + + + + + +
TypeDetails
argsMatchFilter
ReturnsNoneMatch filters to be taken the union of.
+

Usage: The Union filter is usually used when specifying query models.

+
+
union_filter = Union(
+    WeakAnd(hits=10, field="field_name"),
+    ANN(
+        doc_vector="doc_vector",
+        query_vector="query_vector",
+        hits=10,
+        label="label",
+    ),
+)
+
+
+
+
+

Ranking

+
+

source

+
+

Ranking

+
+
 Ranking (name:str='default', list_features:bool=False)
+
+

Define the rank profile to be used during ranking.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
namestrdefaultName of the rank profile as defined in a Vespa search definition.
list_featuresboolFalseShould the ranking features be returned. Either ‘true’ or ‘false’.
ReturnsNone
+

Usage: Ranking is usually used when specifying query models.

+
+
ranking = Ranking(name="bm25", list_features=True)
+
+
+
+
+

Query properties

+
+

source

+
+

QueryProperty

+
+
 QueryProperty ()
+
+

Abstract class for query property.

+
+

source

+
+
+

QueryRankingFeature

+
+
 QueryRankingFeature (name:str, mapping:Callable[[str],List[float]])
+
+

Include ranking.feature.query into a Vespa query.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
namestrName of the feature.
mappingtyping.Callable[[str], typing.List[float]]Function mapping a string to a list of floats.
ReturnsNone
+

Usage: QueryRankingFeature is usually used when specifying query models.

+
+
query_property = QueryRankingFeature(
+    name="query_vector", mapping=lambda x: [1, 2, 3]
+)
+
+
+
+
+

Query model

+
+

source

+
+

QueryModel

+
+
 QueryModel (name:str='default_name',
+             query_properties:Optional[List[__main__.QueryProperty]]=None,
+             match_phase:__main__.MatchFilter=<__main__.AND object at
+             0x7f8fe5e0da30>, ranking:__main__.Ranking=<__main__.Ranking
+             object at 0x7f8fe5e0da90>,
+             body_function:Optional[Callable[[str],Dict]]=None)
+
+

Define a query model.

+

A QueryModel is an abstraction that encapsulates all the relevant information controlling how a Vespa app matches and ranks documents.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
namestrdefault_nameName of the query model. Used to tag model-related quantities, like evaluation metrics.
query_propertiestyping.Optional[typing.List[main.QueryProperty]]NoneQuery properties to be included in the queries.
match_phaseMatchFilter<main.AND object at 0x7f8fe5e0da30>Define the match criteria.
rankingRanking<main.Ranking object at 0x7f8fe5e0da90>Define the rank criteria.
body_functiontyping.Optional[typing.Callable[[str], typing.Dict]]NoneFunction that take query as parameter and returns the body of a Vespa query.
ReturnsNone
+

Usage:

+

Specify a query model with default configurations:

+
+
query_model = QueryModel()
+
+

Specify match phase, ranking phase and properties used by them.

+
+
query_model = QueryModel(
+    query_properties=[
+        QueryRankingFeature(name="query_embedding", mapping=lambda x: [1, 2, 3])
+    ],
+    match_phase=ANN(
+        doc_vector="document_embedding",
+        query_vector="query_embedding",
+        hits=10,
+        label="label",
+    ),
+    ranking=Ranking(name="bm25_plus_embeddings", list_features=True),
+)
+
+

Specify a query model based on a function that output Vespa YQL.

+
+
def body_function(query):
+    body = {
+        "yql": "select * from sources * where userQuery();",
+        "query": query,
+        "type": "any",
+        "ranking": {"profile": "bm25", "listFeatures": "true"},
+    }
+    return body
+
+query_model = QueryModel(body_function=body_function)
+
+
+
+
+

Send query with QueryModel

+
+

source

+
+

send_query

+
+
 send_query (app:vespa.application.Vespa, body:Optional[Dict]=None,
+             query:Optional[str]=None,
+             query_model:Optional[__main__.QueryModel]=None,
+             debug_request:bool=False, recall:Optional[Tuple]=None,
+             **kwargs)
+
+

Send a query request to a Vespa application.

+

Either send ‘body’ containing all the request parameters or specify ‘query’ and ‘query_model’.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appVespaConnection to a Vespa application
bodytyping.Optional[typing.Dict]NoneContains all the request parameters. None when using query_model.
querytyping.Optional[str]NoneQuery string. None when using body.
query_modeltyping.Optional[main.QueryModel]NoneQuery model. None when using body.
debug_requestboolFalseReturn request body for debugging instead of sending the request.
recalltyping.Optional[typing.Tuple]NoneTuple of size 2 where the first element is the name of the field to use to recall and the second element is a list of the values to be recalled.
kwargs
ReturnsVespaQueryResponseEither the request body if debug_request is True or the result from the Vespa application.
+

Usage: Assume app is a Vespa connection.

+

Send request body.

+
+
body = {"yql": "select * from sources * where test"}
+result = send_query(app=app, body=body)
+
+

Use query and query_model:

+
+
result = send_query(
+    app=app,
+    query="this is a test",
+    query_model=QueryModel(
+        match_phase=OR(), 
+        ranking=Ranking()
+    ),
+    hits=10,
+)
+
+

Debug the output of the QueryModel by setting debug_request=True:

+
+
send_query(
+    app=app,
+    query="this is a test",
+    query_model=QueryModel(match_phase=OR(), ranking=Ranking()),
+    debug_request=True,
+    hits=10,
+).request_body
+
+
{'yql': 'select * from sources * where ({grammar: "any"}userInput("this is a test"));',
+ 'ranking': {'profile': 'default', 'listFeatures': 'false'},
+ 'hits': 10}
+
+
+

Recall documents using the id field:

+
+
result = send_query(
+    app=app,
+    query="this is a test",
+    query_model=QueryModel(match_phase=OR(), ranking=Ranking()),
+    hits=10,
+    recall=("id", [1, 5]),
+)
+
+

Use a body_function to specify a QueryModel:

+
+
def body_function(query):
+    body = {
+        "yql": "select * from sources * where userQuery();",
+        "query": query,
+        "type": "any",
+        "ranking": {"profile": "bm25", "listFeatures": "true"},
+    }
+    return body
+
+query_model = QueryModel(body_function=body_function)
+
+result = send_query(
+        app=app,
+        query="this is a test",
+        query_model=query_model,
+        hits=10
+)
+
+
+

source

+
+
+

send_query_batch

+
+
 send_query_batch (app, body_batch:Optional[List[Dict]]=None,
+                   query_batch:Optional[List[str]]=None,
+                   query_model:Optional[__main__.QueryModel]=None,
+                   recall_batch:Optional[List[Tuple]]=None,
+                   asynchronous=True, connections:Optional[int]=100,
+                   total_timeout:int=100, **kwargs)
+
+

Send queries in batch to a Vespa app.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appConnection to a Vespa application
body_batchtyping.Optional[typing.List[typing.Dict]]NoneContains all the request parameters. Set to None if using ‘query_batch’.
query_batchtyping.Optional[typing.List[str]]NoneQuery strings. Set to None if using ‘body_batch’.
query_modeltyping.Optional[main.QueryModel]NoneQuery model to use when sending query strings. Set to None if using ‘body_batch’.
recall_batchtyping.Optional[typing.List[typing.Tuple]]NoneOne tuple for each query. Tuple of size 2 where the first element is the name of the field to use to recall and the second element is a list of the values to be recalled.
asynchronousboolTrueSet True to send data in async mode. Default to True.
connectionstyping.Optional[int]100Number of allowed concurrent connections, valid only if asynchronous=True.
total_timeoutint100Total timeout in secs for each of the concurrent requests when using asynchronous=True.
kwargs
Returnstyping.List[vespa.io.VespaQueryResponse]HTTP POST responses.
+

Use body_batch to send a batch of body requests.

+
+
body_batch = [
+    {"yql": "select * from sources * where test"},
+    {"yql": "select * from sources * where test2"}
+]
+result = send_query_batch(app=app, body_batch=body_batch)
+
+

Use query_batch to send a batch of query strings to be ranked according a QueryModel.

+
+
result = send_query_batch(
+    app=app,
+    query_batch=["this is a test", "this is a test 2"],
+    query_model=QueryModel(
+        match_phase=OR(), 
+        ranking=Ranking()
+    ),
+    hits=10,
+)
+
+

Use recall_batch to send one tuple for each query in query_batch.

+
+
result = send_query_batch(
+    app=app,
+    query_batch=["this is a test", "this is a test 2"],
+    query_model=QueryModel(match_phase=OR(), ranking=Ranking()),
+    hits=10,
+    recall_batch=[("doc_id", [2, 7]), ("doc_id", [0, 5])],
+)
+
+
+
+
+

Collect Vespa features

+
+

source

+
+

collect_vespa_features

+
+
 collect_vespa_features (app:vespa.application.Vespa, labeled_data,
+                         id_field:str, query_model:__main__.QueryModel,
+                         number_additional_docs:int, fields:List[str],
+                         keep_features:Optional[List[str]]=None,
+                         relevant_score:int=1, default_score:int=0,
+                         **kwargs)
+
+

Collect Vespa features based on a set of labelled data.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appVespaConnection to a Vespa application.
labeled_dataLabelled data containing query, query_id and relevant ids. See examples about data format.
id_fieldstrThe Vespa field representing the document id.
query_modelQueryModelQuery model.
number_additional_docsintNumber of additional documents to retrieve for each relevant document. Duplicate documents will be dropped.
fieldstyping.List[str]Vespa fields to collect, e.g. [“rankfeatures”, “summaryfeatures”]
keep_featurestyping.Optional[typing.List[str]]NoneList containing the names of the features that should be returned. Default to None, which return all the features contained in the ‘fields’ argument.
relevant_scoreint1Score to assign to relevant documents. Default to 1.
default_scoreint0Score to assign to the additional documents that are not relevant. Default to 0.
kwargs
ReturnsDataFrameDataFrame containing document id (document_id), query id (query_id), scores (relevant) and vespa rank features returned by the Query model RankProfile used.
+

Usage:

+

Define labeled_data as a list of dict containing relevant documents:

+
+
labeled_data = [
+    {
+        "query_id": 0,
+        "query": "give me title 1",
+        "relevant_docs": [{"id": "1", "score": 1}],
+    },
+    {
+        "query_id": 1,
+        "query": "give me title 3",
+        "relevant_docs": [{"id": "3", "score": 1}],
+    },
+]
+
+

Collect vespa features:

+
+
rank_features = collect_vespa_features(
+    app=app,
+    labeled_data=labeled_data,
+    id_field="doc_id",
+    query_model=QueryModel(
+        match_phase=OR(), 
+        ranking=Ranking(name="bm25", list_features=True)
+    ),
+    number_additional_docs=2,
+    fields=["rankfeatures"],
+)
+rank_features
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelattributeMatch(doc_id)attributeMatch(doc_id).averageWeightattributeMatch(doc_id).completenessattributeMatch(doc_id).fieldCompletenessattributeMatch(doc_id).importanceattributeMatch(doc_id).matchesattributeMatch(doc_id).maxWeight...term(3).significanceterm(3).weightterm(4).connectednessterm(4).significanceterm(4).weighttextSimilarity(text).fieldCoveragetextSimilarity(text).ordertextSimilarity(text).proximitytextSimilarity(text).queryCoveragetextSimilarity(text).score
01010.00.00.00.00.00.00.0...0.583333100.00.00.00.00.501.01.0000000.500.750000
37000.00.00.00.00.00.00.0...0.583333100.00.00.00.00.250.00.8593750.250.425781
13110.00.00.00.00.00.00.0...0.583333100.00.00.00.00.501.01.0000000.500.750000
57100.00.00.00.00.00.00.0...0.583333100.00.00.00.00.250.00.8593750.250.425781
+ +

4 rows × 94 columns

+
+
+
+

Use a DataFrame for labeled_data instead of a list of dict:

+
+
labeled_data = [
+    {
+        "qid": 0,
+        "query": "give me title 1",
+        "doc_id": 1, 
+        "relevance": 1
+    },
+    {
+        "qid": 1,
+        "query": "give me title 3",
+        "doc_id": 3, 
+        "relevance": 1
+    },
+]
+labeled_data_df = DataFrame.from_records(labeled_data)
+labeled_data_df
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
qidquerydoc_idrelevance
00give me title 111
11give me title 331
+ +
+
+
+
+
rank_features = collect_vespa_features(
+    app=app,
+    labeled_data=labeled_data_df,
+    id_field="doc_id",
+    query_model=QueryModel(
+        match_phase=OR(), ranking=Ranking(name="bm25", list_features=True)
+    ),
+    number_additional_docs=2,
+    fields=["rankfeatures"],
+)
+rank_features
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelattributeMatch(doc_id)attributeMatch(doc_id).averageWeightattributeMatch(doc_id).completenessattributeMatch(doc_id).fieldCompletenessattributeMatch(doc_id).importanceattributeMatch(doc_id).matchesattributeMatch(doc_id).maxWeight...term(3).significanceterm(3).weightterm(4).connectednessterm(4).significanceterm(4).weighttextSimilarity(text).fieldCoveragetextSimilarity(text).ordertextSimilarity(text).proximitytextSimilarity(text).queryCoveragetextSimilarity(text).score
01000.00.00.00.00.00.00.0...0.583333100.00.00.00.00.501.01.0000000.500.750000
37000.00.00.00.00.00.00.0...0.583333100.00.00.00.00.250.00.8593750.250.425781
13100.00.00.00.00.00.00.0...0.583333100.00.00.00.00.501.01.0000000.500.750000
57100.00.00.00.00.00.00.0...0.583333100.00.00.00.00.250.00.8593750.250.425781
+ +

4 rows × 94 columns

+
+
+
+

Keep only selected features by specifying their names in the keep_features argument:

+
+
rank_features = collect_vespa_features(
+    app=app,
+    labeled_data=labeled_data_df,
+    id_field="doc_id",
+    query_model=QueryModel(
+        match_phase=OR(), ranking=Ranking(name="bm25", list_features=True)
+    ),
+    number_additional_docs=2,
+    fields=["rankfeatures"],
+    keep_features=["textSimilarity(text).score"],
+)
+rank_features
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabeltextSimilarity(text).score
01000.750000
37000.425781
13100.750000
57100.425781
+ +
+
+
+
+

source

+
+
+

store_vespa_features

+
+
 store_vespa_features (app:vespa.application.Vespa, output_file_path:str,
+                       labeled_data, id_field:str,
+                       query_model:__main__.QueryModel,
+                       number_additional_docs:int, fields:List[str],
+                       keep_features:Optional[List[str]]=None,
+                       relevant_score:int=1, default_score:int=0,
+                       batch_size=1000, **kwargs)
+
+

Retrieve Vespa rank features and store them in a .csv file.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
appVespaConnection to a Vespa application.
output_file_pathstrPath of the .csv output file. It will create the file of it does not exist and append the vespa features to an pre-existing file.
labeled_dataLabelled data containing query, query_id and relevant ids. See details about data format.
id_fieldstrThe Vespa field representing the document id.
query_modelQueryModelQuery model.
number_additional_docsintNumber of additional documents to retrieve for each relevant document.
fieldstyping.List[str]List of Vespa fields to collect, e.g. [“rankfeatures”, “summaryfeatures”]
keep_featurestyping.Optional[typing.List[str]]NoneList containing the names of the features that should be returned. Default to None, which return all the features contained in the ‘fields’ argument.
relevant_scoreint1Score to assign to relevant documents.
default_scoreint0Score to assign to the additional documents that are not relevant.
batch_sizeint1000The size of the batch of labeled data points to be processed.
kwargs
Returnsintreturns 0 upon success.
+

Usage:

+
+
labeled_data = [
+    {
+        "query_id": 0,
+        "query": "give me title 1",
+        "relevant_docs": [{"id": "1", "score": 1}],
+    },
+    {
+        "query_id": 1,
+        "query": "give me title 3",
+        "relevant_docs": [{"id": "3", "score": 1}],
+    },
+]
+
+store_vespa_features(
+    app=app,
+    output_file_path="vespa_features.csv",
+    labeled_data=labeled_data,
+    id_field="doc_id",
+    query_model=QueryModel(
+        match_phase=OR(), ranking=Ranking(name="bm25", list_features=True)
+    ),
+    number_additional_docs=2,
+    fields=["rankfeatures", "summaryfeatures"],
+)
+rank_features = read_csv("vespa_features.csv")
+rank_features
+
+
Rows collected: 4.
+Batch progress: 1/1.
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelattributeMatch(doc_id)attributeMatch(doc_id).averageWeightattributeMatch(doc_id).completenessattributeMatch(doc_id).fieldCompletenessattributeMatch(doc_id).importanceattributeMatch(doc_id).matchesattributeMatch(doc_id).maxWeight...term(3).weightterm(4).connectednessterm(4).significanceterm(4).weighttextSimilarity(text).fieldCoveragetextSimilarity(text).ordertextSimilarity(text).proximitytextSimilarity(text).queryCoveragetextSimilarity(text).scorevespa.summaryFeatures.cached
01010.00.00.00.00.00.00.0...100.00.00.00.00.501.01.0000000.500.7500000.0
17000.00.00.00.00.00.00.0...100.00.00.00.00.250.00.8593750.250.4257810.0
23110.00.00.00.00.00.00.0...100.00.00.00.00.501.01.0000000.500.7500000.0
37100.00.00.00.00.00.00.0...100.00.00.00.00.250.00.8593750.250.4257810.0
+ +

4 rows × 95 columns

+
+
+
+ + +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/module_ranking.html b/module_ranking.html new file mode 100644 index 0000000..38cb6fa --- /dev/null +++ b/module_ranking.html @@ -0,0 +1,791 @@ + + + + + + + + + + +Vespa Data Science - ranking + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

ranking

+
+ +
+
+ Reference API related to the ranking framework +
+
+ + +
+ + + + +
+ + +
+ + +
+

source

+
+

keras_linear_model

+
+
 keras_linear_model (number_documents_per_query, number_features)
+
+

linear model with a lasso constrain on the kernel weights.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
number_documents_per_queryNumber of documents per query to reshape the listwise prediction.
number_featuresNumber of features used per document.
ReturnsSequentialThe uncompiled Keras model.
+

Usage:

+
+
klm = keras_linear_model(
+    number_documents_per_query=10, 
+    number_features=5
+)
+
+
+

source

+
+
+

keras_lasso_linear_model

+
+
 keras_lasso_linear_model (number_documents_per_query, number_features,
+                           l1_penalty, normalization_layer:Optional=None)
+
+

linear model with a lasso constrain on the kernel weights.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
number_documents_per_queryNumber of documents per query to reshape the listwise prediction.
number_featuresNumber of features used per document.
l1_penaltyControls the L1-norm penalty.
normalization_layertyping.OptionalNoneInitialized normalization layers. Used when performing feature selection.
ReturnsSequentialThe uncompiled Keras model.
+

Usage:

+
+
kllm = keras_lasso_linear_model(
+    number_documents_per_query=10, 
+    number_features=5, 
+    l1_penalty=0.01
+)
+
+
+

source

+
+
+

keras_ndcg_compiled_model

+
+
 keras_ndcg_compiled_model (model, learning_rate, top_n)
+
+

Compile listwise Keras model with NDCG stateless metric and ApproxNDCGLoss

+ + + + + + + + + + + + + + + + + + + + + +
Details
modelUncompiled Keras model
learning_rateLearning rate used in the Adagrad optim algo.
top_nTop n used when computing the NDCG metric
+

Usage:

+
+
compiled_klm = keras_ndcg_compiled_model(
+    model=klm, 
+    learning_rate=0.1, 
+    top_n=10
+)
+
+
+

source

+
+
+

LinearHyperModel

+
+
 LinearHyperModel (number_documents_per_query, number_features, top_n=10,
+                   learning_rate_range=None)
+
+

Define a KerasTuner search space for linear models

+
+
linear_hyper_model = LinearHyperModel(
+    number_documents_per_query=10, 
+    number_features=10, 
+    top_n=10, 
+    learning_rate_range=[1e-2, 1e2]
+)
+
+
+

source

+
+
+

LassoHyperModel

+
+
 LassoHyperModel (number_documents_per_query, number_features,
+                  trained_normalization_layer, top_n=10,
+                  l1_penalty_range=None, learning_rate_range=None)
+
+

Define a KerasTuner search space for lasso models

+
+

source

+
+
+

ListwiseRankingFramework

+
+
 ListwiseRankingFramework (number_documents_per_query, batch_size=32,
+                           shuffle_buffer_size=1000, tuner_max_trials=3,
+                           tuner_executions_per_trial=1, tuner_epochs=1,
+                           tuner_early_stop_patience=None, final_epochs=1,
+                           top_n=10, l1_penalty_range=None,
+                           learning_rate_range=None, folder_dir='/home/run
+                           ner/work/learntorank/learntorank')
+
+

Listwise ranking framework

+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/module_stats.html b/module_stats.html new file mode 100644 index 0000000..482be4d --- /dev/null +++ b/module_stats.html @@ -0,0 +1,1383 @@ + + + + + + + + + + +Vespa Data Science - stats + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

stats

+
+ +
+
+ Reference API related to statistical functions +
+
+ + +
+ + + + +
+ + +
+ + +
+

Bootstrap estimates

+
+

source

+
+

bootstrap_sampling

+
+
 bootstrap_sampling (data:pandas.core.frame.DataFrame,
+                     estimator:Callable=<function mean>, n_boot:int=1000,
+                     columns_to_exclude:List[str]=None)
+
+

Compute bootstrap estimates of the data distribution

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
dataDataFrameData containing the columns we want to generate bootstrap estimates from.
estimatortyping.Callablemeanestimator function that accepts an array-like argument.
n_bootint1000Number of bootstrap estimates to compute.
columns_to_excludetyping.List[str]NoneColumn names to exclude.
+

Usage:

+

Generate data with columns containing data that we want to compute estimates from. The values in the column a comes from Normal distribution with mean 0 and standard deviation 1. The values from column b comes from Normal distribution with mean 100 and standard deviation 10.

+
+
data = pd.DataFrame(
+    data={
+        "a": np.random.normal(size = 100), 
+        "b": np.random.normal(loc=100, scale = 10, size = 100)
+    }
+)
+data.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ab
00.60563992.817505
1-0.77579192.750026
2-1.265231107.981771
30.981306101.388385
40.029075122.700172
+ +
+
+
+
+

Compute mean of the distribution by default

+

By default, the function generates the mean of each column n_boot times. Each value represents the mean obtained from a bootstrap sample of the original data.

+
+
estimates = bootstrap_sampling(data, n_boot=100)
+estimates
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ab
00.012356100.018394
10.143189100.691872
2-0.00255499.874399
30.07939599.539636
40.055096100.452383
.........
950.063409100.439363
96-0.02445598.607045
970.20942799.866736
980.06132398.680469
990.28945699.980295
+ +

100 rows × 2 columns

+
+
+
+

We can check if the estimates make sense by compute the mean of the bootstrap estimates and comparing with the mean of the Normal distribution they were generated from.

+
+
estimates.mean()
+
+
a      0.089538
+b    100.099900
+dtype: float64
+
+
+
+
+

Specify function. Example: Standard deviation.

+

We can specify other functions, such as np.std to compute the standard deviation.

+
+
estimates = bootstrap_sampling(data, estimator=np.std, n_boot=100)
+estimates
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ab
00.93349610.126658
10.9291259.852667
20.89976210.307814
30.96803910.416074
41.00434910.441463
.........
950.91090410.357727
960.81827612.358640
970.9818269.622724
980.96223710.897055
990.91399411.096338
+ +

100 rows × 2 columns

+
+
+
+

If we take the mean of the bootstrap estimates of the standard deviation, we should recover a value close to the standard deviation of the distribution that the data were generated from.

+
+
estimates.mean()
+
+
a     0.943942
+b    10.480457
+dtype: float64
+
+
+
+
+

Exclude unwanted columns

+
+
estimates = bootstrap_sampling(
+    data, n_boot=100, columns_to_exclude=["b"]
+)
+estimates
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
a
00.259128
10.098232
20.087111
3-0.131376
40.050997
......
950.129835
96-0.004873
97-0.046338
980.246239
990.355848
+ +

100 rows × 1 columns

+
+
+
+
+

source

+
+
+
+

compute_evaluation_estimates

+
+
 compute_evaluation_estimates (df:pandas.core.frame.DataFrame,
+                               n_boot:int=1000,
+                               estimator:Callable=<function mean>,
+                               quantile_low:float=0.025,
+                               quantile_high=0.975)
+
+

Compute estimate and confidence interval for evaluation per query metrics.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
dfDataFrameEvaluations per query data, usually obtained pyvespa evaluate method.
n_bootint1000Number of bootstrap samples.
estimatortyping.Callablemeanestimator function that accepts an array-like argument.
quantile_lowfloat0.025lower quantile to compute confidence interval
quantile_highfloat0.975upper quantile to compute confidence interval
+

Usage:

+

Generate sample data frame, which must contain the column model.

+
+
number_data_points = 1000
+data = pd.DataFrame(
+    data = {
+        "model": (
+            ["A"] * number_data_points + 
+            ["B"] * number_data_points
+        ),
+        "query_id": (
+            list(range(number_data_points)) + 
+            list(range(number_data_points))
+        ),
+        "metric_1": (
+            np.random.binomial(size=number_data_points, n=1, p=0.3).tolist() + 
+            np.random.binomial(size=number_data_points, n=1, p=0.7).tolist()
+        ),
+        "metric_2": (
+            np.random.binomial(size=number_data_points, n=1, p=0.1).tolist() + 
+            np.random.binomial(size=number_data_points, n=1, p=0.9).tolist()
+        )
+        
+    }
+).sort_values("query_id").reset_index(drop=True)
+data
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelquery_idmetric_1metric_2
0A000
1B011
2A101
3B111
4A200
...............
1995A99710
1996B99811
1997A99810
1998A99900
1999B99901
+ +

2000 rows × 4 columns

+
+
+
+
+

Compute the confidence interval of the mean by default

+
+
compute_evaluation_estimates(data)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
metricmodellowmedianhigh
0metric_1A0.2680000.2960.325
1metric_1B0.6670000.6960.724
2metric_2A0.0910000.1090.129
3metric_2B0.8879750.9070.924
+ +
+
+
+
+
+

Specify function. Example: Standard deviation.

+
+
compute_evaluation_estimates(data, estimator=np.std)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
metricmodellowmedianhigh
0metric_1A0.4429180.4564910.468375
1metric_1B0.4480010.4599830.470931
2metric_2A0.2890260.3116390.335200
3metric_2B0.2649980.2918290.315366
+ +
+
+
+
+
+

Specify interval coverage

+
+
compute_evaluation_estimates(
+    data, 
+    quantile_low=0.2, 
+    quantile_high=0.8
+)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
metricmodellowmedianhigh
0metric_1A0.2850.2960.308
1metric_1B0.6840.6960.708
2metric_2A0.1020.1100.118
3metric_2B0.8980.9060.914
+ +
+
+
+
+
compute_evaluation_estimates(data[["model", "metric_1", "metric_2"]])
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
metricmodellowmedianhigh
0metric_1A0.2699750.2970.326000
1metric_1B0.6679750.6960.726000
2metric_2A0.0910000.1090.129025
3metric_2B0.8880000.9070.923000
+ +
+
+
+ + +
+
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/collect-training-data.html b/notebooks/collect-training-data.html new file mode 100644 index 0000000..4e59566 --- /dev/null +++ b/notebooks/collect-training-data.html @@ -0,0 +1,869 @@ + + + + + + + + + + +Vespa Data Science - Collect training data from application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Collect training data from application

+
+ +
+
+ Collect training data to analyse and/or improve ranking functions +
+
+ + +
+ + + + +
+ + +
+ + +
+

Example setup

+

Connect to the application and define a query model.

+
+
from vespa.application import Vespa
+from learntorank.query import QueryModel, Ranking, OR
+
+app = Vespa(url = "https://api.cord19.vespa.ai")
+query_model = QueryModel(
+    match_phase = OR(),
+    ranking = Ranking(name="bm25", list_features=True)
+)
+
+

Define some labeled data.

+
+
labeled_data = [
+    {
+        "query_id": 0, 
+        "query": "Intrauterine virus infections and congenital heart disease",
+        "relevant_docs": [{"id": 0, "score": 1}, {"id": 3, "score": 1}]
+    },
+    {
+        "query_id": 1, 
+        "query": "Clinical and immunologic studies in identical twins discordant for systemic lupus erythematosus",
+        "relevant_docs": [{"id": 1, "score": 1}, {"id": 5, "score": 1}]
+    }
+]
+
+
+
+

Collect training data in batch

+
+
from learntorank.query import collect_vespa_features
+
+training_data_batch = collect_vespa_features(
+    app=app,
+    labeled_data = labeled_data,
+    id_field = "id",
+    query_model = query_model,
+    number_additional_docs = 2,
+    fields=["rankfeatures"]
+)
+training_data_batch
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelattributeMatch(authors.first)attributeMatch(authors.first).averageWeightattributeMatch(authors.first).completenessattributeMatch(authors.first).fieldCompletenessattributeMatch(authors.first).importanceattributeMatch(authors.first).matchesattributeMatch(authors.first).maxWeight...textSimilarity(results).fieldCoveragetextSimilarity(results).ordertextSimilarity(results).proximitytextSimilarity(results).queryCoveragetextSimilarity(results).scoretextSimilarity(title).fieldCoveragetextSimilarity(title).ordertextSimilarity(title).proximitytextSimilarity(title).queryCoveragetextSimilarity(title).score
00010.00.00.00.00.00.00.0...0.00.00.00.00.00.0625000.00.00000.1428570.055357
13010.00.00.00.00.00.00.0...0.00.00.00.00.00.1428570.00.43750.1428570.224554
4255164000.00.00.00.00.00.00.0...0.00.00.00.00.01.0000001.01.00001.0000001.000000
5120761000.00.00.00.00.00.00.0...0.00.00.00.00.00.3000001.01.00000.4285710.688571
21110.00.00.00.00.00.00.0...0.00.00.00.00.00.1111110.00.00000.0833330.047222
35110.00.00.00.00.00.00.0...0.00.00.00.00.00.0833330.00.00000.0833330.041667
8232555100.00.00.00.00.00.00.0...0.00.00.00.00.01.0000001.01.00001.0000001.000000
913944100.00.00.00.00.00.00.0...0.00.00.00.00.00.1875001.01.00000.2500000.612500
+ +

8 rows × 1038 columns

+
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/compare-pre-trained-clip-for-text-image-search.html b/notebooks/compare-pre-trained-clip-for-text-image-search.html new file mode 100644 index 0000000..f93f994 --- /dev/null +++ b/notebooks/compare-pre-trained-clip-for-text-image-search.html @@ -0,0 +1,745 @@ + + + + + + + + + +Vespa Data Science - Compare pre-trained CLIP models for text-image retrieval + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Compare pre-trained CLIP models for text-image retrieval

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

Get the Vespa sample application, install requirements:

+
+
! git clone --depth 1 https://github.com/vespa-engine/sample-apps.git
+
+! pip install -r requirements.txt
+
+

There are multiple CLIP model variations:

+
+
import clip
+
+clip.available_models()
+
+

To limit run-time of this notebook, do not use all models - modify the set:

+
+
# Limit number of models to test - here, first two models
+use_models = clip.available_models()[0:2]
+
+

Each model has an embedding size, needed in the text-image search application schema:

+
+
embedding_info = {name: clip.load(name)[0].visual.output_dim for name in use_models}
+embedding_info
+
+
+

Create and deploy a text-image search app

+
+

Create the Vespa application package

+

The function create_text_image_app below uses the Vespa python API to create an application package with fields to store each of the different types of image embedding associated with the CLIP models. It also declares the types of the text embeddings that we are going to send along with the query when searching for images, and creates one ranking profile for each (text, image) embedding model:

+
+
from embedding import create_text_image_app
+
+app_package = create_text_image_app(embedding_info)
+
+

Inspect the schema of the resulting application package:

+
+
print(app_package.schema.schema_to_text)
+
+
+
+

Deploy

+
+
import os
+from vespa.deployment import VespaDocker
+
+vespa_docker = VespaDocker()
+app = vespa_docker.deploy(application_package=app_package)
+
+
+
+
+

Compute and feed image embeddings

+

Get a sample data set. See download_flickr8k.sh for how to download images. Set location of images:

+

For each of the CLIP models, compute the image embeddings and send it to the Vespa app:

+
+
from embedding import compute_and_send_image_embeddings
+
+compute_and_send_image_embeddings(app=app, batch_size=128, clip_model_names=use_models)
+
+
+
+

Define QueryModel’s to be evaluated

+

Create one QueryModel for each of the CLIP models. In order to do that, we need to have a function that takes a query as input and outputs the body function of a Vespa query request - example:

+
+
from embedding import create_vespa_query_body_function
+
+vespa_query_body_function = create_vespa_query_body_function("RN50")
+vespa_query_body_function("this is a test query")["yql"]
+
+

With a method to create Vespa query body functions, we can create QueryModels that will be used to evaluate each search configuration that is to be tested. In this case, each query model will represent a CLIP model text-image representation:

+
+
from learntorank.query import QueryModel
+
+query_models = [QueryModel(
+    name=model_name, 
+    body_function=create_vespa_query_body_function(model_name)
+) for model_name in use_models]
+
+

A query model contains all the information that is necessary to define how the search app will match and rank documents. Use it to query the application:

+
+
from embedding import plot_images
+from learntorank.query import send_query
+
+query_result = send_query(app, query="a person surfing", query_model=query_models[-1], hits = 4)
+
+

To inspect the results, use query_result.hits[0]. Display top two:

+
+
from IPython.display import Image, display
+
+image_file_names = [ hit["fields"]["image_file_name"] for hit in query_result.hits[:2] ]
+
+for image in image_file_names:
+    display(Image(filename=os.path.join(os.environ["IMG_DIR"], image)))
+
+
+
+

Evaluate

+

Now that there is one QueryModel for each CLIP model available, it is posible to evaluate and compare them.

+

Define search evaluation metrics:

+
+
from learntorank.evaluation import MatchRatio, Recall, ReciprocalRank
+
+eval_metrics = [
+    MatchRatio(), # Match ratio is just to show the % of documents that are matched by ANN
+    Recall(at=100), 
+    ReciprocalRank(at=100)
+]
+
+

Load labeled data. It was assumed that a (caption, image) pair is relevant if all three experts agreed that the caption accurately described the image:

+
+
from pandas import read_csv
+
+labeled_data = read_csv("https://data.vespa.oath.cloud/blog/flickr8k/labeled_data.csv", sep = "\t")
+labeled_data.head()
+
+

Evaluate the application and return per query results:

+
+
from learntorank.evaluation import evaluate
+
+result = evaluate(
+    app=app,
+    labeled_data=labeled_data, 
+    eval_metrics=eval_metrics, 
+    query_model=query_models, 
+    id_field="image_file_name",
+    per_query=True
+)
+result.head()
+
+

Visualize RR@100:

+
+
import matplotlib.pyplot as plt
+
+plt.plot(result.reciprocal_rank_100)
+plt.ylabel("reciprocal_rank_100")
+plt.show()
+
+

Compute mean and median across models:

+
+
result[["model", "reciprocal_rank_100"]].groupby(
+    "model"
+).agg(
+    Mean=('reciprocal_rank_100', 'mean'), 
+    Median=('reciprocal_rank_100', 'median')
+)
+
+
+
+

Cleanup

+

Stop and remove the Docker container:

+
+
vespa_docker.container.stop()
+vespa_docker.container.remove()
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/cord19/cord19_connect_evaluate.html b/notebooks/cord19/cord19_connect_evaluate.html new file mode 100644 index 0000000..2669138 --- /dev/null +++ b/notebooks/cord19/cord19_connect_evaluate.html @@ -0,0 +1,937 @@ + + + + + + + + + + +Vespa Data Science - How to evaluate Vespa ranking functions from python + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

How to evaluate Vespa ranking functions from python

+
+ +
+
+ Using pyvespa to evaluate cord19 search application ranking functions currently in production. +
+
+ + +
+ + + + +
+ + +
+ + +
+

Download processed data

+

We can start by downloading the data that we have processed before.

+
+
import requests, json
+from pandas import read_csv
+
+topics = json.loads(
+    requests.get("https://thigm85.github.io/data/cord19/topics.json").text
+)
+relevance_data = read_csv("https://thigm85.github.io/data/cord19/relevance_data.csv")
+
+

topics contain data about the 50 topics available, including query, question and narrative.

+
+
topics["1"]
+
+
{'query': 'coronavirus origin',
+ 'question': 'what is the origin of COVID-19',
+ 'narrative': "seeking range of information about the SARS-CoV-2 virus's origin, including its evolution, animal source, and first transmission into humans"}
+
+
+

relevance_data contains the relevance judgments for each of the 50 topics.

+
+
relevance_data.head(5)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
topic_idround_idcord_uidrelevancy
014.5005b2j4b2
114.000fmeepz1
210.5010vptx32
312.50194oljo1
414.0021q98841
+ +
+
+
+
+
+

Format the labeled data into expected pyvespa format

+

pyvespa expects labeled data to follow the format illustrated below. It is a list of dict where each dict represents a query containing query_id, query and a list of relevant_docs. Each relevant document contains a required id key and an optional score key.

+
+
labeled_data = [
+    {
+        'query_id': 1,
+        'query': 'coronavirus origin',
+        'relevant_docs': [{'id': '005b2j4b', 'score': 2}, {'id': '00fmeepz', 'score': 1}]
+    },
+    {
+        'query_id': 2,
+        'query': 'coronavirus response to weather changes',
+        'relevant_docs': [{'id': '01goni72', 'score': 2}, {'id': '03h85lvy', 'score': 2}]
+    }
+]
+
+

We can create labeled_data from the topics and relevance_data that we downloaded before. We are only going to include documents with relevance score > 0 into the final list.

+
+
labeled_data = [
+    {
+        "query_id": int(topic_id), 
+        "query": topics[topic_id]["query"], 
+        "relevant_docs": [
+            {
+                "id": row["cord_uid"], 
+                "score": row["relevancy"]
+            } for idx, row in relevance_data[relevance_data.topic_id == int(topic_id)].iterrows() if row["relevancy"] > 0
+        ]
+    } for topic_id in topics.keys()]
+
+
+
+

Define query models to be evaluated

+

We are going to define two query models to be evaluated here. Both will match all the documents that share at least one term with the query. This is defined by setting match_phase = OR().

+

The difference between the query models happens in the ranking phase. The or_default model will rank documents based on nativeRank while the or_bm25 model will rank documents based on BM25. Discussion about those two types of ranking is out of the scope of this tutorial. It is enough to know that they rank documents according to two different formulas.

+

Those ranking profiles were defined by the team behind the cord19 app and can be found here.

+
+
from learntorank.query import QueryModel, Ranking, OR
+
+query_models = [
+    QueryModel(
+        name="or_default",
+        match_phase = OR(),
+        ranking = Ranking(name="default")
+    ),
+    QueryModel(
+        name="or_bm25",
+        match_phase = OR(),
+        ranking = Ranking(name="bm25t5")
+    )
+]
+
+
+
+

Define metrics to be used in the evaluation

+

We would like to compute the following metrics:

+
    +
  • The percentage of documents matched by the query

  • +
  • Recall @ 10

  • +
  • Reciprocal rank @ 10

  • +
  • NDCG @ 10

  • +
+
+
from learntorank.evaluation import MatchRatio, Recall, ReciprocalRank, NormalizedDiscountedCumulativeGain
+
+eval_metrics = [
+    MatchRatio(), 
+    Recall(at=10), 
+    ReciprocalRank(at=10), 
+    NormalizedDiscountedCumulativeGain(at=10)
+]
+
+
+
+

Evaluate

+

Connect to a running Vespa instance:

+
+
from vespa.application import Vespa
+
+app = Vespa(url = "https://api.cord19.vespa.ai")
+
+

Compute the metrics defined above for each query model.

+
+
from learntorank.evaluation import evaluate
+evaluations = evaluate(
+    app=app,
+    labeled_data = labeled_data,
+    eval_metrics = eval_metrics,
+    query_model = query_models,
+    id_field = "cord_uid",
+    hits = 10
+)
+evaluations
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelor_bm25or_default
match_ratiomean0.4117890.411789
median0.2822270.282227
std0.2385020.238502
recall_10mean0.0077200.005457
median0.0060890.003753
std0.0063860.005458
reciprocal_rank_10mean0.5943570.561579
median0.5000000.500000
std0.3975970.401255
ndcg_10mean0.3530950.274515
median0.3559780.253619
std0.2164600.203170
+ +
+
+
+

We can also return per query raw evaluation metrics:

+
+
evaluations = evaluate(
+    app=app,
+    labeled_data = labeled_data,
+    eval_metrics = eval_metrics,
+    query_model = query_models,
+    id_field = "cord_uid",
+    hits = 10,
+    per_query = True
+)
+evaluations.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelquery_idmatch_ratiorecall_10reciprocal_rank_10ndcg_10
0or_default10.2308470.0085841.0000000.519431
1or_default20.7552300.0000000.0000000.000000
2or_default30.2646010.0015340.1428570.036682
3or_default40.8433410.0017640.3333330.110046
4or_default50.9013170.0030960.2500000.258330
+ +
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/cord19/cord19_download_parse_trec_covid.html b/notebooks/cord19/cord19_download_parse_trec_covid.html new file mode 100644 index 0000000..57b3928 --- /dev/null +++ b/notebooks/cord19/cord19_download_parse_trec_covid.html @@ -0,0 +1,749 @@ + + + + + + + + + + +Vespa Data Science - How to download and parse TREC-COVID data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

How to download and parse TREC-COVID data

+
+ +
+
+ Your first step to contribute to the improvement of the cord19 search application. +
+
+ + +
+ + + + +
+ + +
+ + +
+

Download the data

+

The files used in this section were originally found at https://ir.nist.gov/covidSubmit/data.html. We will download both the topics and the relevance judgements data. Do not worry about what they are just yet, we will explore them soon.

+
+
!curl -fsSLO https://data.vespa.oath.cloud/blog/cord19/topics-rnd5.xml
+!curl -fsSLO https://data.vespa.oath.cloud/blog/cord19/qrels-covid_d5_j0.5-5.txt
+
+
+
+

Parse the data

+
+

Topics

+

The topics file is in XML format. We can parse it and store in a dictionary called topics. We want to extract a query, a question and a narrative from each topic.

+
+
import xml.etree.ElementTree as ET
+
+topics = {}
+root = ET.parse("topics-rnd5.xml").getroot()
+for topic in root.findall("topic"):
+    topic_number = topic.attrib["number"]
+    topics[topic_number] = {}
+    for query in topic.findall("query"):
+        topics[topic_number]["query"] = query.text
+    for question in topic.findall("question"):
+        topics[topic_number]["question"] = question.text        
+    for narrative in topic.findall("narrative"):
+        topics[topic_number]["narrative"] = narrative.text
+
+

There are a total of 50 topics. For example, we can see the first topic below:

+
+
topics["1"]
+
+
{'query': 'coronavirus origin',
+ 'question': 'what is the origin of COVID-19',
+ 'narrative': "seeking range of information about the SARS-CoV-2 virus's origin, including its evolution, animal source, and first transmission into humans"}
+
+
+

Each topic has many relevance judgements associated with them.

+
+
+

Relevance judgements

+

We can load the relevance judgement data directly into a pandas DataFrame.

+
+
import pandas as pd
+
+relevance_data = pd.read_csv("qrels-covid_d5_j0.5-5.txt", sep=" ", header=None)
+relevance_data.columns = ["topic_id", "round_id", "cord_uid", "relevancy"]
+
+

The relevance data contains all the relevance judgements made throughout the 5 rounds of the competition. relevancy equals to 0 is irrelevant, 1 is relevant and 2 is highly relevant.

+
+
relevance_data.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
topic_idround_idcord_uidrelevancy
014.5005b2j4b2
114.000fmeepz1
210.5010vptx32
312.50194oljo1
414.0021q98841
+ +
+
+
+

We are going to remove two rows that have relevancy equal to -1, which I am assuming is an error.

+
+
relevance_data[relevance_data.relevancy == -1]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
topic_idround_idcord_uidrelevancy
55873385.09hbib8b3-1
69173505.0ucipq8uk-1
+ +
+
+
+
+
relevance_data = relevance_data[relevance_data.relevancy >= 0]
+
+

Next we will discuss how we can use this data to evaluate and improve cord19 search app.

+ + +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/evaluation.html b/notebooks/evaluation.html new file mode 100644 index 0000000..f8671a4 --- /dev/null +++ b/notebooks/evaluation.html @@ -0,0 +1,822 @@ + + + + + + + + + + +Vespa Data Science - Evaluate application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Evaluate application

+
+ +
+
+ Define metrics and evaluate query models +
+
+ + +
+ + + + +
+ + +
+ + +
+

Example setup

+

Connect to the application and define a query model.

+
+
from vespa.application import Vespa
+from learntorank.query import QueryModel, Ranking, OR
+
+app = Vespa(url = "https://api.cord19.vespa.ai")
+query_model = QueryModel(
+    match_phase = OR(),
+    ranking = Ranking(name="bm25", list_features=True))
+
+
+
+

Labeled data

+

Define some labeled data. pyvespa expects labeled data to follow the format illustrated below. It is a list of dict where each dict represents a query containing query_id, query and a list of relevant_docs. Each relevant document contain a required id key and an optional score key.

+
+
labeled_data = [
+    {
+        "query_id": 0, 
+        "query": "Intrauterine virus infections and congenital heart disease",
+        "relevant_docs": [{"id": 0, "score": 1}, {"id": 3, "score": 1}]
+    },
+    {
+        "query_id": 1, 
+        "query": "Clinical and immunologic studies in identical twins discordant for systemic lupus erythematosus",
+        "relevant_docs": [{"id": 1, "score": 1}, {"id": 5, "score": 1}]
+    }
+]
+
+
+
+

Define metrics

+
+
from learntorank.evaluation import MatchRatio, Recall, ReciprocalRank
+
+eval_metrics = [MatchRatio(), Recall(at=10), ReciprocalRank(at=10)]
+
+
+
+

Evaluate in batch

+
+
from learntorank.evaluation import evaluate
+
+evaluation = evaluate(
+    app=app,
+    labeled_data = labeled_data,
+    eval_metrics = eval_metrics, 
+    query_model = query_model, 
+    id_field = "id",
+)
+evaluation
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modeldefault_name
match_ratiomean0.853456
median0.853456
std0.055199
recall_10mean0.000000
median0.000000
std0.000000
reciprocal_rank_10mean0.000000
median0.000000
std0.000000
+ +
+
+
+
+
+

Evaluate specific query

+
+

You can have finer control with the evaluate_query method.

+
+
+
from pandas import concat, DataFrame
+from learntorank.evaluation import evaluate_query
+
+evaluation = []
+for query_data in labeled_data:
+    query_evaluation = evaluate_query(
+        app=app,
+        eval_metrics = eval_metrics, 
+        query_model = query_model, 
+        query_id = query_data["query_id"], 
+        query = query_data["query"], 
+        id_field = "id",
+        relevant_docs = query_data["relevant_docs"],
+        default_score = 0
+    )
+    evaluation.append(query_evaluation)
+evaluation = DataFrame.from_records(evaluation)
+evaluation
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelquery_idmatch_ratiorecall_10reciprocal_rank_10
0default_name00.8144250.00
1default_name10.8924870.00
+ +
+
+
+
+
+

Evaluate query under specific document ids

+
+

Use recall to specify which documents should be inlcuded in the evaluation

+
+

In the example below, we include documents with id equal to 0, 1 and 2. Since the relevant documents for this query are the documents with id 0 and 3, we should get recall equal to 0.5.

+
+
query_evaluation = evaluate_query(
+    app=app,
+    eval_metrics = eval_metrics, 
+    query_model = query_model, 
+    query_id = 0, 
+    query = "Intrauterine virus infections and congenital heart disease", 
+    id_field = "id",
+    relevant_docs = [{"id": 0, "score": 1}, {"id": 3, "score": 1}],
+    default_score = 0,
+    recall = ("id", [0, 1, 2])
+)
+query_evaluation
+
+
{'model': 'default_name',
+ 'query_id': 0,
+ 'match_ratio': 9.70242657688688e-06,
+ 'recall_10': 0.5,
+ 'reciprocal_rank_10': 1.0}
+
+
+

We now include documents with id equal to 0, 1, 2 and 3. This should give a recall equal to 1.

+
+
query_evaluation = evaluate_query(
+    app=app,
+    eval_metrics = eval_metrics, 
+    query_model = query_model, 
+    query_id = 0, 
+    query = "Intrauterine virus infections and congenital heart disease", 
+    id_field = "id",
+    relevant_docs = [{"id": 0, "score": 1}, {"id": 3, "score": 1}],
+    default_score = 0,
+    recall = ("id", [0, 1, 2, 3])
+)
+query_evaluation
+
+
{'model': 'default_name',
+ 'query_id': 0,
+ 'match_ratio': 1.2936568769182506e-05,
+ 'recall_10': 1.0,
+ 'reciprocal_rank_10': 1.0}
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/image_search/clip-evaluation-boxplot.png b/notebooks/image_search/clip-evaluation-boxplot.png new file mode 100644 index 0000000..b859bc3 Binary files /dev/null and b/notebooks/image_search/clip-evaluation-boxplot.png differ diff --git a/notebooks/image_search/demo.gif b/notebooks/image_search/demo.gif new file mode 100644 index 0000000..c443289 Binary files /dev/null and b/notebooks/image_search/demo.gif differ diff --git a/notebooks/image_search/image-search-scratch.html b/notebooks/image_search/image-search-scratch.html new file mode 100644 index 0000000..9784029 --- /dev/null +++ b/notebooks/image_search/image-search-scratch.html @@ -0,0 +1,736 @@ + + + + + + + + + +Vespa Data Science - Image search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Image search

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

This notebook walks through the pyvespa code used to create the text to image search sample application.

+
+

ToDo: This notebook is still work in progress and cannot yet be auto-run

+
+
+
+

+
SegmentLocal
+
+
+
+

Create the application package

+

Create an application package:

+
+
from vespa.package import ApplicationPackage
+
+app_package = ApplicationPackage(name="imagesearch")
+
+

Add a field to hold the name of the image file. This is used in the sample app to load the final images that should be displayed to the end user.

+

The summary indexing ensures this field is returned as part of the query response. The attribute indexing store the fields in memory as an attribute for sorting, querying, and grouping:

+
+
from vespa.package import Field
+
+app_package.schema.add_fields(
+    Field(name="image_file_name", type="string", indexing=["summary", "attribute"]),
+)
+
+

Add a field to hold an image embedding. The embeddings are usually generated by a ML model. We can add multiple embedding fields to our application. This is useful when making experiments. For example, the sample app adds 6 image embeddings, one for each of the six pre-trained CLIP models available at the time.

+

In the example below, the embedding vector has size 512 and is of type float. The index is required to enable approximate matching and the HNSW instance configure the HNSW index:

+
+
from vespa.package import HNSW
+
+app_package.schema.add_fields(
+    Field(
+        name="embedding_image",
+        type="tensor<float>(x[512])",
+        indexing=["attribute", "index"],
+        ann=HNSW(
+            distance_metric="angular",
+            max_links_per_node=16,
+            neighbors_to_explore_at_insert=500,
+        ),
+    )
+)
+
+

Add a rank profile that ranks the images by how close the image embedding vector is from the query embedding vector. The tensors used in queries must have their type declared in the application package, the code below declares the text embedding that will be sent in the query - it has the same size and type of the image embedding:

+
+
from vespa.package import RankProfile
+
+app_package.schema.add_rank_profile(
+    RankProfile(
+        name="embedding_similarity",
+        inherits="default",
+        first_phase="closeness(embedding_image)",
+        inputs=[("query(embedding_text)", "tensor<float>(x[512])")],
+    )
+)
+
+
+
+

Deploy the application

+

The application package created above can be deployed using Docker or Vespa Cloud. Follow the instructions based on the desired deployment mode. Either option will create a Vespa connection instance that can be stored in a variable that will be denoted here as app.

+

We can then use app to interact with the deployed application:

+
+
import os
+from vespa.deployment import VespaDocker
+
+vespa_docker = VespaDocker(
+    port=8080
+)
+
+app = vespa_docker.deploy(application_package = app_package)
+
+
Waiting for configuration server, 0/300 seconds...
+Waiting for configuration server, 5/300 seconds...
+Waiting for application status, 0/300 seconds...
+Waiting for application status, 5/300 seconds...
+Waiting for application status, 10/300 seconds...
+Waiting for application status, 15/300 seconds...
+Waiting for application status, 20/300 seconds...
+Waiting for application status, 25/300 seconds...
+Finished deployment.
+
+
+
+
+

Feed the image data

+

ToDo: Add code below to create the feed and set batch - until then, disabled auto testing.

+

To feed the image data:

+
+
responses = app.feed_batch(batch)
+
+

where batch is a list of dictionaries like the one below:

+
+
{
+    "id": "dog1",
+    "fields": {
+        "image_file_name": "dog1.jpg",
+        "embedding_image": {"values": [0.884, -0.345, ..., 0.326]},
+    }
+}
+
+

One of the advantages of having a python API is that it can integrate with commonly used ML frameworks. The sample application show how to create a PyTorch DataLoader to generate batches of image data by using CLIP models to generate image embeddings.

+
+
+

Query the application

+

The following query will use approximate nearest neighbor search to match the closest images to the query text and rank the images according to their distance to the query text. The sample application used CLIP models to generate image and query embeddings.

+
+
response = app.query(body={
+    "yql": 'select * from sources * where ({targetHits:100}nearestNeighbor(embedding_image,embedding_text));',
+    "hits": 100,
+    "input.query(embedding_text)": [0.632, -0.987, ..., 0.534],
+    "ranking.profile": "embedding_similarity"
+})
+
+
+
+

Evaluate different query models

+

Define metrics to evaluate:

+
+
from learntorank.evaluation import MatchRatio, Recall, ReciprocalRank
+
+eval_metrics = [
+    MatchRatio(), 
+    Recall(at=100), 
+    ReciprocalRank(at=100)
+]
+
+

The sample application illustrates how to evaluate different CLIP models through the evaluate method:

+
+
result = app.evaluate(
+    labeled_data=labeled_data,  # Labeled data to define which images should be returned to a given query
+    eval_metrics=eval_metrics,  # Metrics used
+    query_model=query_models,   # Each query model uses a different CLIP model version
+    id_field="image_file_name", # The name of the id field used by the labeled data to identify the image
+    per_query=True              # Return results per query rather the aggragated.
+)
+
+

The figure below is the reciprocal rank at 100 computed based on the output of the evaluate method.

+
+
+

+
evaluation
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/learning-to-rank-ignore.html b/notebooks/learning-to-rank-ignore.html new file mode 100644 index 0000000..13647e9 --- /dev/null +++ b/notebooks/learning-to-rank-ignore.html @@ -0,0 +1,1520 @@ + + + + + + + + + + +Vespa Data Science - Learning to rank + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Learning to rank

+
+ +
+
+ Data pipelines, model fitting and feature selection +
+
+ + +
+ + + + +
+ + +
+ + +
+
+

+
Vespa logo
+
+
+

This notebook is WIP and not runnable - ToDo FIXME

+
+

Data

+

This section describes the data that we are going to use to give a brief overview of the pyvespa ranking framework. The data was collected from a running Vespa application indexed with MS MARCO data. For each relevant (document_id, query_id)-pair we collected 9 random matched documents. Relevant documents have label=1 and non-relevant documents have label=0. In addition, many Vespa ranking features computed based on document and query interaction are included.

+
+
train_df = pd.read_csv("https://data.vespa.oath.cloud/blog/ranking/train_sample.csv")
+
+

The data used here is a sample containing 100.000 rows and 71 features.

+
+
train_df.shape
+
+
(100000, 74)
+
+
+
+
train_df.head(10)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelelementCompleteness(body).completenesselementCompleteness(body).fieldCompletenesselementCompleteness(body).queryCompletenessfieldMatch(body)fieldMatch(body).absoluteOccurrencefieldMatch(body).absoluteProximityfieldMatch(body).completeness...term(3).significanceterm(3).weightterm(4).connectednessterm(4).significanceterm(4).weighttextSimilarity(body).fieldCoveragetextSimilarity(body).ordertextSimilarity(body).proximitytextSimilarity(body).queryCoveragetextSimilarity(body).score
027061300.3587960.0925930.6250.1277460.0220000.026000.598380...0.504935100.00.10.674337100.00.0925930.2500000.4375000.6250.396644
1257300.3596700.0943400.6250.0923190.0180000.035000.598467...0.504935100.00.10.674337100.00.0943400.7500000.2343750.6250.400899
2363300.2773970.0547950.5000.1415110.0300000.071000.477740...0.504935100.00.10.674337100.00.0547950.6666670.6406250.5000.485178
322682300.3336860.0423730.6250.2508170.0560000.100000.595869...0.504935100.00.10.674337100.00.0423730.2500000.3242190.6250.346951
4160300.2954550.0909090.5000.1183510.0150000.050000.479545...0.504935100.00.10.674337100.00.0909090.6666670.5572920.5000.463234
5228300.2863640.0727270.5000.1486120.0150000.100000.478636...0.504935100.00.10.674337100.00.0727270.0000000.2864580.5000.264806
63901893300.4338240.1176470.7500.3452560.0250000.077000.718382...0.504935100.00.10.674337100.00.1176470.6000000.5750000.7500.539779
71142680310.4120370.0740740.7500.3431200.0466670.077000.716204...0.504935100.00.10.674337100.00.0740740.6000000.6156250.7500.545284
8141300.2863640.0727270.5000.0814610.0275000.100000.478636...0.504935100.00.10.674337100.00.0727270.6666670.4062500.5000.406733
93060834300.4102940.0705880.7500.3082500.0450000.066750.716029...0.504935100.00.10.674337100.00.0705880.4000000.7156250.7500.549586
+ +

10 rows × 74 columns

+
+
+
+

Similarly, we collected data based on the MS MARCO queries contained on the dev set.

+
+
dev_df = pd.read_csv("https://data.vespa.oath.cloud/blog/ranking/dev_sample.csv")
+
+
+
dev_df.shape
+
+
(74103, 72)
+
+
+
+
dev_df.head(10)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelelementCompleteness(body).completenesselementCompleteness(body).fieldCompletenesselementCompleteness(body).queryCompletenessfieldMatch(body)fieldMatch(body).absoluteOccurrencefieldMatch(body).absoluteProximityfieldMatch(body).completeness...term(3).significanceterm(3).weightterm(4).connectednessterm(4).significanceterm(4).weighttextSimilarity(body).fieldCoveragetextSimilarity(body).ordertextSimilarity(body).proximitytextSimilarity(body).queryCoveragetextSimilarity(body).score
08066640200.3809520.0952380.6666670.4273440.010.10.638095...0.00.00.00.00.00.0952381.01.00.6666670.719048
14339068210.3466670.0266670.6666670.4449330.040.10.634667...0.00.00.00.00.00.0266671.01.00.6666670.705333
2762768200.3437500.0208330.6666670.0888590.010.10.634375...0.00.00.00.00.00.0208331.00.00.6666670.354167
33370200.1801800.0270270.3333330.1620490.010.10.318018...0.00.00.00.00.00.0270270.00.00.3333330.105405
46060200.1752870.0172410.3333330.1457220.010.10.317529...0.00.00.00.00.00.0172410.00.00.3333330.103448
53798200.1805560.0277780.3333330.1669420.010.10.318056...0.00.00.00.00.00.0277780.00.00.3333330.105556
62731175200.3458330.0250000.6666670.3988000.010.10.634583...0.00.00.00.00.00.0250001.01.00.6666670.705000
73634083200.3511900.0357140.6666670.4236110.020.10.635119...0.00.00.00.00.00.0357141.01.00.6666670.707143
8112126200.1762820.0192310.3333330.1770090.020.10.317628...0.00.00.00.00.00.0192310.00.00.3333330.103846
93387200.1785710.0238100.3333330.1713570.010.10.317857...0.00.00.00.00.00.0238100.00.00.3333330.104762
+ +

10 rows × 72 columns

+
+
+
+
+
+

Listwise ranking framework

+

The ListwiseRankingFramework uses TensorFlow Ranking to minimize a listwise loss function that is a smooth approximation of the NDCG metric. The following parameters need to be specified:

+
+
from learntorank.ranking import ListwiseRankingFramework
+
+ranking_framework = ListwiseRankingFramework(
+    #
+    # Task related 
+    #
+    number_documents_per_query=10,  # The size of the list for each sample
+    top_n=10,                       # What NDCG position we want to optmize, e.g. NDCG@10
+    #
+    # Data pipeline 
+    #
+    batch_size=32,                  # Batch size used when fitting models to the data
+    shuffle_buffer_size=1000,       # The buffer size used when shuffling data batches.
+    #
+    # Hyperparameter tuning 
+    #
+    tuner_max_trials=3,             # How many trials to execute when search hyperparameters
+    tuner_executions_per_trial=1,   # How may model fit per trial
+    tuner_epochs=10,                # How many epochs to use per execution of the trial
+    tuner_early_stop_patience=None, # Set patience number for early stopping
+    #
+    # Final model
+    #
+    final_epochs=30                 # Number of epochs to use when fitting the model with specific hyperparameters.
+)
+
+
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
+INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)
+
+
+
+

Data pipeline

+

It is possible to create TensorFlow data pipelines (tf.data.Dataset) either from in-memory data frames or directly from .csv files to avoid the need to load large file into memory. The data pipelines are suited for listwise ranking and can be used as part of a custom tensorflow workflow if desired.

+

Create a tf.data.Dataset from in-memory data frames:

+
+
tf_ds = ranking_framework.listwise_tf_dataset_from_df(
+    df=train_df, 
+    feature_names=["nativeFieldMatch", "nativeProximity", "nativeRank"],
+    shuffle_buffer_size=3,
+    batch_size=1
+)
+
+

Note that the is already suited for listwise learning.

+
+
for batch in tf_ds.take(1):
+    print(batch)
+
+
(<tf.Tensor: shape=(1, 10, 3), dtype=float32, numpy=
+array([[[1.9765680e-01, 6.5953881e-02, 9.5175676e-02],
+        [1.3242842e-01, 1.1140537e-01, 7.1235448e-02],
+        [3.4112938e-02, 1.2160993e-37, 1.5161305e-02],
+        [1.5705481e-01, 4.0344268e-02, 7.4284837e-02],
+        [8.6454414e-02, 3.2825880e-02, 4.2071503e-02],
+        [1.9139472e-01, 1.1913208e-01, 9.8301217e-02],
+        [4.8045117e-02, 1.2160993e-37, 2.1353386e-02],
+        [1.4903504e-01, 1.3032080e-01, 8.0717884e-02],
+        [6.3953400e-02, 2.8740479e-02, 3.1617120e-02],
+        [1.5656856e-01, 6.8069249e-02, 7.7149279e-02]]], dtype=float32)>, <tf.Tensor: shape=(1, 10), dtype=float32, numpy=array([[0., 0., 0., 0., 0., 0., 0., 1., 0., 0.]], dtype=float32)>)
+
+
+

For large data, we can also create a listwise tf.data.Dataset directly from a .csv file, without the need to load it into memory:

+
+
train_df.to_csv("train_sample.csv", index=False)
+
+
+
tf_ds = ranking_framework.listwise_tf_dataset_from_csv(
+    file_path="train_sample.csv",
+    feature_names=["nativeFieldMatch", "nativeProximity", "nativeRank"],
+    shuffle_buffer_size=3,
+    batch_size=1
+)
+
+
+
for batch in tf_ds.take(1):
+    print(batch)
+
+
(<tf.Tensor: shape=(1, 10, 3), dtype=float32, numpy=
+array([[[0.08348585, 0.04784278, 0.04242069],
+        [0.08451388, 0.01466913, 0.03919163],
+        [0.07139124, 0.02419666, 0.03441796],
+        [0.07348892, 0.02119719, 0.03501699],
+        [0.11205826, 0.10210748, 0.06114895],
+        [0.06779736, 0.02308168, 0.03269679],
+        [0.08361208, 0.00839302, 0.03809348],
+        [0.13477945, 0.13513905, 0.07491743],
+        [0.17734438, 0.18263273, 0.09911225],
+        [0.12978926, 0.15896696, 0.07534712]]], dtype=float32)>, <tf.Tensor: shape=(1, 10), dtype=float32, numpy=array([[0., 0., 0., 0., 0., 0., 0., 0., 1., 0.]], dtype=float32)>)
+
+
+
+
+

Pre-defined models

+

The ranking framework comes with same pre-defined models in case you don’t want to use the data pipelines to create your own workflow. It is possible to specify either a DataFrame or a .csv file path as the train and dev input data. If the hyperparameters argument is not specified it will search through the hyperparameter space accordinng to the arguments defined when creating and instance of the ListwiseRankingFramework.

+
+

Linear model

+
+
weights, dev_eval, best_hyperparams = ranking_framework.fit_linear_model(
+    train_data=train_df, 
+    dev_data=dev_df, 
+    feature_names=[
+        "fieldMatch(body).proximity",
+        "fieldMatch(body).queryCompleteness",
+        "fieldMatch(body).significance",
+        "nativeFieldMatch",
+        "nativeProximity",
+        "nativeRank",
+    ],
+    hyperparameters=None # Search for best hyperparameters
+)
+
+
+
best_hyperparams
+
+
{'learning_rate': 6.018683626059954}
+
+
+
+
weights
+
+
{'feature_names': ['fieldMatch(body).proximity',
+  'fieldMatch(body).queryCompleteness',
+  'fieldMatch(body).significance',
+  'nativeFieldMatch',
+  'nativeProximity',
+  'nativeRank'],
+ 'linear_model_weights': [0.46931159496307373,
+  -30.97307014465332,
+  28.785017013549805,
+  18.257308959960938,
+  12.566983222961426,
+  10.918502807617188]}
+
+
+
+
dev_eval
+
+
0.7916887402534485
+
+
+

If we instead specify the hyperpameters, hyperparameter search will be skipped.

+
+
weights, dev_eval, best_hyperparams = ranking_framework.fit_linear_model(
+    train_data=train_df, 
+    dev_data=dev_df, 
+    feature_names=[
+        "fieldMatch(body).proximity",
+        "fieldMatch(body).queryCompleteness",
+        "fieldMatch(body).significance",
+        "nativeFieldMatch",
+        "nativeProximity",
+        "nativeRank",
+    ],
+    hyperparameters={'learning_rate': 6.018683626059954} 
+)
+
+
+
+

Lasso model

+
+
weights, dev_eval, best_hyperparams = ranking_framework.fit_lasso_linear_model(
+    train_data=train_df, 
+    dev_data=dev_df, 
+    feature_names=[
+        "fieldMatch(body).proximity",
+        "fieldMatch(body).queryCompleteness",
+        "fieldMatch(body).significance",
+        "nativeFieldMatch",
+        "nativeProximity",
+        "nativeRank",
+    ]
+)
+
+
+
print(best_hyperparams)
+
+
{'lambda': 0.0023227311360666802, 'learning_rate': 0.14885653869373894}
+
+
+
+
print(weights)
+
+
{'feature_names': ['fieldMatch(body).proximity', 'fieldMatch(body).queryCompleteness', 'fieldMatch(body).significance', 'nativeFieldMatch', 'nativeProximity', 'nativeRank'], 'normalization_mean': [0.8184928894042969, 0.530807375907898, 0.5052036643028259, 0.0906180813908577, 0.039063721895217896, 0.04461509734392166], 'normalization_sd': [0.08662283420562744, 0.05760122463107109, 0.06236378848552704, 0.003072209656238556, 0.003147233510389924, 0.0008713427814655006], 'normalization_number_data': 96990, 'linear_model_weights': [-0.022373167797923088, -2.1850321292877197, 2.055746078491211, 0.21248634159564972, 0.2774745225906372, 0.6118378043174744]}
+
+
+
+
print(dev_eval)
+
+
0.7700856328010559
+
+
+
+
+
+

Feature selection

+

The are some pre-defined algorithms that can be used for feature selection. The goal is to find a subset of features that are responsible for most of the evaluation metric gains.

+ +
+

Forward selection

+

Incrementally add one feature at a time and keep the features that maximize the validation metric.

+
+
forward_results = ranking_framework.forward_selection_model_search(
+    train_data=train_df, 
+    dev_data=dev_df, 
+    feature_names=[
+        "fieldMatch(body).proximity",
+        "fieldMatch(body).queryCompleteness",
+        "fieldMatch(body).significance",
+        "nativeFieldMatch",
+        "nativeProximity",
+        "nativeRank",
+    ],
+    output_file="forward_model_search.json",
+)
+
+

Evaluation metric for one feature model.

+
+
[
+    (result["evaluation"], result["weights"]["feature_names"]) for 
+     result in forward_results 
+     if result["number_features"] == 1
+]
+
+
[(0.4771268367767334, ['fieldMatch(body).proximity']),
+ (0.5774978995323181, ['fieldMatch(body).queryCompleteness']),
+ (0.3523213565349579, ['fieldMatch(body).significance']),
+ (0.693596601486206, ['nativeFieldMatch']),
+ (0.673930287361145, ['nativeProximity']),
+ (0.704784631729126, ['nativeRank'])]
+
+
+

Evaluation metric for two features keeping the best feature of the oe-feature model

+
+
[
+    (result["evaluation"], result["weights"]["feature_names"]) for 
+     result in forward_results 
+     if result["number_features"] == 2
+]
+
+
[(0.7052107453346252, ['nativeRank', 'fieldMatch(body).proximity']),
+ (0.7083131670951843, ['nativeRank', 'fieldMatch(body).queryCompleteness']),
+ (0.7050297260284424, ['nativeRank', 'fieldMatch(body).significance']),
+ (0.7048313617706299, ['nativeRank', 'nativeFieldMatch']),
+ (0.7088075876235962, ['nativeRank', 'nativeProximity'])]
+
+
+

And so on:

+
+
[
+    (result["evaluation"], result["weights"]["feature_names"]) for 
+     result in forward_results 
+     if result["number_features"] == 3
+]
+
+
[(0.7087035179138184,
+  ['nativeRank', 'nativeProximity', 'fieldMatch(body).proximity']),
+ (0.7237873673439026,
+  ['nativeRank', 'nativeProximity', 'fieldMatch(body).queryCompleteness']),
+ (0.7073785662651062,
+  ['nativeRank', 'nativeProximity', 'fieldMatch(body).significance']),
+ (0.709153413772583, ['nativeRank', 'nativeProximity', 'nativeFieldMatch'])]
+
+
+ + +
+
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/query-model.html b/notebooks/query-model.html new file mode 100644 index 0000000..8ec3e35 --- /dev/null +++ b/notebooks/query-model.html @@ -0,0 +1,711 @@ + + + + + + + + + +Vespa Data Science - Query models + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Query models

+
+ + + +
+ + + + +
+ + +
+ + +
+
from learntorank.query import QueryModel, Ranking, OR
+
+standard_query_model = QueryModel(
+    name="or_bm25",
+    match_phase = OR(),
+    ranking = Ranking(name="bm25")
+)
+
+

Starting in version 0.5.0 we can bypass the pyvespa high-level API and create a QueryModel with the full flexibility of the Vespa Query API. This is useful for use cases not covered by the pyvespa API and for users that are familiar with and prefer to work with the Vespa Query API.

+
+
def body_function(query):
+    body = {'yql': 'select * from sources * where userQuery();',
+            'query': query,
+            'type': 'any',
+            'ranking': {'profile': 'bm25', 'listFeatures': 'false'}}
+    return body
+
+flexible_query_model = QueryModel(body_function = body_function)
+
+

The flexible_query_model defined above is equivalent to the standard_query_model, as we can see when querying the app. We will use the cord19 app in our demonstration.

+
+
from vespa.application import Vespa
+
+app = Vespa(url = "https://api.cord19.vespa.ai")
+
+
+
from learntorank.query import send_query
+
+standard_result = send_query(
+    app=app, 
+    query="this is a test", 
+    query_model=standard_query_model
+)
+standard_result.get_hits().head(3)
+
+
+
flexible_result = send_query(
+    app=app, 
+    query="this is a test", 
+    query_model=flexible_query_model
+)
+flexible_result.get_hits().head(3)
+
+
+

Specify a query model

+
+

Query + term-matching + rank profile

+
+
from learntorank.query import QueryModel, OR, Ranking, send_query
+
+results = send_query(
+    app=app,
+    query="Is remdesivir an effective treatment for COVID-19?", 
+    query_model = QueryModel(
+        match_phase=OR(), 
+        ranking=Ranking(name="bm25")
+    )
+)
+
+
+
results.number_documents_retrieved
+
+
+
+

Query + term-matching + ann operator + rank_profile

+
+
from learntorank.query import QueryModel, QueryRankingFeature, ANN, WeakAnd, Union, Ranking
+from random import random
+
+match_phase = Union(
+    WeakAnd(hits = 10), 
+    ANN(
+        doc_vector="specter_embedding", 
+        query_vector="specter_vector", 
+        hits = 10,
+        label="title"
+    )
+)
+ranking = Ranking(name="related-specter", list_features=True)
+query_model = QueryModel(
+    query_properties=[QueryRankingFeature(
+        name="specter_vector", 
+        mapping=lambda x: [random() for x in range(768)]
+    )],
+    match_phase=match_phase, ranking=ranking
+)
+
+
+
results = send_query(
+    app=app,
+    query="Is remdesivir an effective treatment for COVID-19?", 
+    query_model=query_model
+)
+
+
+
results.number_documents_retrieved
+
+
+
+
+

Recall specific documents

+

Let’s take a look at the top 3 ids from the last query.

+
+
top_ids = [hit["fields"]["id"] for hit in results.hits[0:3]]
+top_ids
+
+

Assume that we now want to retrieve the second and third ids above. We can do so with the recall argument.

+
+
results_with_recall = send_query(
+    app=app,
+    query="Is remdesivir an effective treatment for COVID-19?", 
+    query_model=query_model,
+    recall = ("id", top_ids[1:3])
+)
+
+

It will only retrieve the documents with Vespa field id that is defined on the list that is inside the tuple.

+
+
id_recalled = [hit["fields"]["id"] for hit in results_with_recall.hits]
+id_recalled
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/notebooks/tensorflow-via-onnx.html b/notebooks/tensorflow-via-onnx.html new file mode 100644 index 0000000..10651a3 --- /dev/null +++ b/notebooks/tensorflow-via-onnx.html @@ -0,0 +1,1434 @@ + + + + + + + + + +Vespa Data Science - TensorFlow: Deploy model to Vespa through ONNX + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

TensorFlow: Deploy model to Vespa through ONNX

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

This tutorial will cover the following steps:

+
    +
  1. Download labeled data containing Vespa ranking features.
  2. +
  3. Create a listwise dataset based on a TensorFlow data pipeline.
  4. +
  5. Train a Learning to Rank model (LTR) model using the TensorFlow Ranking framework.
  6. +
  7. Simplify the LTR model to be suitable for ranking in Vespa
  8. +
  9. Convert to TensorFlow model to ONNX file format.
  10. +
  11. Create and deploy a Vespa application that uses the TensorFlow model
  12. +
  13. Feed data to the Vespa application
  14. +
  15. Ensure that prediction from the model deployed to Vespa match those obtained from the model directly.
  16. +
+
+

Install packages

+
+
!pip3 install -Uqq pyvespa learntorank numpy==1.23.5 pandas tensorflow tensorflow_ranking onnx tf2onnx
+
+
+
+

Get the data

+
+
import pandas as pd
+
+

Download labeled data containing Vespa ranking features collected from an MS Marco passage ranking application.

+
+
df = pd.read_csv("https://data.vespa.oath.cloud/blog/ranking/train_sample.csv")
+df = df[
+    ["document_id", 
+     "query_id", 
+     "label", 
+     "fieldMatch(body).queryCompleteness",
+     "fieldMatch(body).significance",
+     "nativeRank",
+    ]
+]
+
+
+
df.shape
+
+
(100000, 6)
+
+
+

For each query_id, there is 9 irrelevant document_id with label = 0 and 1 relevant document_id with label = 1.

+
+
df.head(10)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
document_idquery_idlabelfieldMatch(body).queryCompletenessfieldMatch(body).significancenativeRank
027061300.6250.5663110.042421
1257300.6250.5825700.039192
2363300.5000.4660300.034418
322682300.6250.5663110.061149
4160300.5000.4378080.035017
5228300.5000.4378080.032697
63901893300.7500.7480640.074917
71142680310.7500.7480640.099112
8141300.5000.4428790.038093
93060834300.7500.7639330.075347
+ +
+
+
+
+
+

Create a listwise dataset

+

Define some parameters required to setup the listwise data pipeline.

+
+
number_documents_per_query = 10            
+feature_names = [                         
+    "fieldMatch(body).queryCompleteness", 
+    "fieldMatch(body).significance", 
+    "nativeRank"
+]
+number_features = len(feature_names)
+batch_size=32
+
+

Each feature data point will have the shape equal to (batch_size, number_documents_per_query, number_features) and each label data point will have shape equal to (batch_size, number_documents_per_query).

+
+
import tensorflow as tf
+
+

The code below creates a TensorFlow data pipeline (tf.data.Dataset) from our DataFrame and group the rows by the query_id variable to form a listwise dataset. We then configure the data pipeline to shuffle and set a batch size.

+
+
shuffle_buffer_size = 10000
+ds = tf.data.Dataset.from_tensor_slices(
+    {
+        "features": tf.cast(df[feature_names].values, tf.float32),
+        "label": tf.cast(df["label"].values, tf.float32),
+        "query_id": tf.cast(df["query_id"].values, tf.int64),
+    }
+)
+
+key_func = lambda x: x["query_id"]
+reduce_func = lambda key, dataset: dataset.batch(
+    number_documents_per_query, drop_remainder=True
+)
+listwise_ds = ds.group_by_window(
+    key_func=key_func,
+    reduce_func=reduce_func,
+    window_size=number_documents_per_query,
+)
+listwise_ds = listwise_ds.map(lambda x: (x["features"], x["label"]))
+listwise_ds = listwise_ds.shuffle(buffer_size=shuffle_buffer_size).batch(
+    batch_size=batch_size
+)
+
+

We can see the shape of the features and of the labels are as expected.

+
+
for d in listwise_ds.take(1):
+    print(d[0].shape)
+    print(d[1].shape)
+
+
(32, 10, 3)
+(32, 10)
+
+
+
+
+

Create and compile model

+

We are going to create a linear model that can take a listwise data as input with shape (batch_size, number_documents_per_query, number_features) and output one prediction per document with shape (batch_size, number_documents_per_query)

+
+
input_layer = tf.keras.layers.Input(shape=(number_documents_per_query, number_features))
+dense_layer = tf.keras.layers.Dense(
+    1,
+    use_bias=False,
+    activation=None,
+    name="dense"
+)
+output_layer = tf.keras.layers.Reshape((number_documents_per_query,))
+
+
+
model = tf.keras.Sequential(layers=[input_layer, dense_layer, output_layer])
+
+

In this tutorial, we want to optimize the Normalized Discounted Cumulative Gain at position 10 (NDCG@10). We then select a loss function that is a smooth approximation of the NDCG metric and create a stateless NDCG@10 metric to use when compiling the model defined above.

+
+
import tensorflow_ranking as tfr
+
+ndcg = tfr.keras.metrics.NDCGMetric(topn=10)
+def ndcg_stateless(y_true, y_pred):
+    """
+    Create stateless metric so that we can compute the validation metric 
+    from scratch at the end of each epoch.
+    """
+    ndcg.reset_states()
+    return ndcg(y_true, y_pred)
+
+optimizer = tf.keras.optimizers.Adagrad(learning_rate=2)
+model.compile(
+    optimizer=optimizer,
+    loss=tfr.keras.losses.ApproxNDCGLoss(),
+    metrics=ndcg_stateless,
+)
+
+

Use the listwise dataset to fit the model:

+
+
history = model.fit(listwise_ds, epochs=20)
+
+
Epoch 1/20
+304/304 [==============================] - 8s 3ms/step - loss: -0.6522 - ndcg_stateless: 0.6874
+Epoch 2/20
+304/304 [==============================] - 1s 921us/step - loss: -0.6959 - ndcg_stateless: 0.7159
+Epoch 3/20
+304/304 [==============================] - 1s 905us/step - loss: -0.7001 - ndcg_stateless: 0.7166
+Epoch 4/20
+304/304 [==============================] - 1s 904us/step - loss: -0.7025 - ndcg_stateless: 0.7168
+Epoch 5/20
+304/304 [==============================] - 1s 901us/step - loss: -0.7043 - ndcg_stateless: 0.7165
+Epoch 6/20
+304/304 [==============================] - 1s 920us/step - loss: -0.7106 - ndcg_stateless: 0.7242
+Epoch 7/20
+304/304 [==============================] - 1s 903us/step - loss: -0.7355 - ndcg_stateless: 0.7647
+Epoch 8/20
+304/304 [==============================] - 1s 898us/step - loss: -0.7399 - ndcg_stateless: 0.7662
+Epoch 9/20
+304/304 [==============================] - 1s 923us/step - loss: -0.7430 - ndcg_stateless: 0.7679
+Epoch 10/20
+304/304 [==============================] - 1s 911us/step - loss: -0.7450 - ndcg_stateless: 0.7679
+Epoch 11/20
+304/304 [==============================] - 1s 955us/step - loss: -0.7464 - ndcg_stateless: 0.7682
+Epoch 12/20
+304/304 [==============================] - 1s 914us/step - loss: -0.7475 - ndcg_stateless: 0.7683
+Epoch 13/20
+304/304 [==============================] - 1s 919us/step - loss: -0.7485 - ndcg_stateless: 0.7689
+Epoch 14/20
+304/304 [==============================] - 1s 909us/step - loss: -0.7493 - ndcg_stateless: 0.7682
+Epoch 15/20
+304/304 [==============================] - 1s 904us/step - loss: -0.7499 - ndcg_stateless: 0.7692
+Epoch 16/20
+304/304 [==============================] - 1s 900us/step - loss: -0.7506 - ndcg_stateless: 0.7691
+Epoch 17/20
+304/304 [==============================] - 1s 893us/step - loss: -0.7513 - ndcg_stateless: 0.7699
+Epoch 18/20
+304/304 [==============================] - 1s 1ms/step - loss: -0.7516 - ndcg_stateless: 0.7694
+Epoch 19/20
+304/304 [==============================] - 1s 910us/step - loss: -0.7520 - ndcg_stateless: 0.7694
+Epoch 20/20
+304/304 [==============================] - 1s 830us/step - loss: -0.7524 - ndcg_stateless: 0.7686
+
+
+
+
+

Simplify model input/output for deployment

+

After training the model by minimizing a listwise loss function, we can simplify the model before deploying it to Vespa. At inference time, Vespa will evaluate each document individually and use a ranking function to rank documents.

+

Therefore, the input layer will expect a tensor named input with shape equal to (1, number_features).

+
+
simpler_model = tf.keras.Sequential(
+    [tf.keras.layers.Input(shape=(number_features,), batch_size=1, name="input"), 
+     dense_layer
+    ]
+)
+
+

We are going to save the simpler_model to disk and then use the tf2onnx tool to convert the model to ONNX format.

+
+
simpler_model.save("simpler_keras_model")
+
+
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
+INFO:tensorflow:Assets written to: simpler_keras_model/assets
+
+
+
INFO:tensorflow:Assets written to: simpler_keras_model/assets
+
+
+
+
from tf2onnx import convert
+
+!python3 -m tf2onnx.convert --saved-model simpler_keras_model --output simpler_keras_model.onnx
+
+
<frozen runpy>:128: RuntimeWarning: 'tf2onnx.convert' found in sys.modules after import of package 'tf2onnx', but prior to execution of 'tf2onnx.convert'; this may result in unpredictable behaviour
+2023-08-08 14:09:40,224 - WARNING - '--tag' not specified for saved_model. Using --tag serve
+2023-08-08 14:09:40,328 - INFO - Signatures found in model: [serving_default].
+2023-08-08 14:09:40,328 - WARNING - '--signature_def' not specified, using first signature: serving_default
+2023-08-08 14:09:40,328 - INFO - Output names: ['dense']
+2023-08-08 14:09:40,328 - WARNING - Could not search for non-variable resources. Concrete function internal representation may have changed.
+WARNING:tensorflow:From /usr/local/lib/python3.11/site-packages/tf2onnx/tf_loader.py:557: extract_sub_graph (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.
+Instructions for updating:
+This API was designed for TensorFlow v1. See https://www.tensorflow.org/guide/migrate for instructions on how to migrate your code to TensorFlow v2.
+2023-08-08 14:09:40,379 - WARNING - From /usr/local/lib/python3.11/site-packages/tf2onnx/tf_loader.py:557: extract_sub_graph (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.
+Instructions for updating:
+This API was designed for TensorFlow v1. See https://www.tensorflow.org/guide/migrate for instructions on how to migrate your code to TensorFlow v2.
+2023-08-08 14:09:40,388 - INFO - Using tensorflow=2.13.0, onnx=1.14.0, tf2onnx=1.8.4/cd55bf
+2023-08-08 14:09:40,388 - INFO - Using opset <onnx, 9>
+2023-08-08 14:09:40,389 - INFO - Computed 0 values for constant folding
+2023-08-08 14:09:40,395 - INFO - Optimizing ONNX model
+2023-08-08 14:09:40,402 - INFO - After optimization: Identity -5 (5->0)
+2023-08-08 14:09:40,403 - INFO - 
+2023-08-08 14:09:40,403 - INFO - Successfully converted TensorFlow model simpler_keras_model to ONNX
+2023-08-08 14:09:40,403 - INFO - Model inputs: ['input:0']
+2023-08-08 14:09:40,403 - INFO - Model outputs: ['dense']
+2023-08-08 14:09:40,403 - INFO - ONNX model is saved at simpler_keras_model.onnx
+
+
+

We can inspect the onnx model input and output. We first load the ONNX model:

+
+
import onnx                  
+
+m = onnx.load("simpler_keras_model.onnx")
+
+

As mentioned before, the model expects a tensor named input with shape (1, 3).

+
+
m.graph.input
+
+
[name: "input"
+type {
+  tensor_type {
+    elem_type: 1
+    shape {
+      dim {
+        dim_value: 1
+      }
+      dim {
+        dim_value: 3
+      }
+    }
+  }
+}
+]
+
+
+

The output will be a tensor named dense with shape (1,1).

+
+
m.graph.output
+
+
[name: "dense"
+type {
+  tensor_type {
+    elem_type: 1
+    shape {
+      dim {
+        dim_value: 1
+      }
+      dim {
+        dim_value: 1
+      }
+    }
+  }
+}
+]
+
+
+
+
+

Define the application package

+

This section will use the Vespa python API pyvespa to create an application package with a ranking function that uses the tensorflow model exported to ONNX.

+

The data used to train the model was derived from a Vespa application based on the MS Marco passage dataset. So, we are going to name the application msmarco, and start by adding two fields: id to hold the document id and text to hold the passages from the msmarco dataset.

+

indexing configuration: We add "summary" to the indexing parameter because we want to include both the id and the text field in the query results. The "attribute" indicates that the field id will be stored in-memory. The "index" indicates that Vespa will create a search index for the text field.

+
+
from vespa.package import ApplicationPackage, Field
+
+app_package = ApplicationPackage(name="msmarco")
+
+app_package.schema.add_fields(
+    Field(name="id", type="string", indexing=["summary", "attribute"]),
+    Field(name="text", type="string", indexing=["summary", "index"])
+)
+
+

Note that at each step along the application package definition, we can inspect the content of the Vespa search definition file:

+
+
print(app_package.schema.schema_to_text)
+
+
schema msmarco {
+    document msmarco {
+        field id type string {
+            indexing: summary | attribute
+        }
+        field text type string {
+            indexing: summary | index
+        }
+    }
+}
+
+
+

Add simpler_keras_model.onnx to the schema. * The model_name is an id that can be used in the ranking function to identify which model to use. * The model_file_path is the current path of the .onnx file. When deploying the application, pyvespa will move the file to the correct location inside the Vespa application package folder. * The inputs maps the name of the inputs contained in the ONNX model to the name of the Vespa source that will be used as input to the model. In this case we will create a function called vespa_input that output a tensor of type float with the expected shape (1, 3). * The outputs maps the output name in the ONNX file to the output name that will be recognized by Vespa.

+
+
from vespa.package import OnnxModel
+
+app_package.schema.add_model(
+    OnnxModel(
+        model_name="ltr_tensorflow",
+        model_file_path="simpler_keras_model.onnx",
+        inputs={"input": "vespa_input"},
+        outputs={"dense": "dense"},
+    )
+)
+
+

It is possible to see the addition of the onnx-model section in the search definition below. Note that the model file is expected to be under the files folder inside the final application package folder, but pyvespa takes care of the model file placement when deploying the application.

+
+
print(app_package.schema.schema_to_text)
+
+
schema msmarco {
+    document msmarco {
+        field id type string {
+            indexing: summary | attribute
+        }
+        field text type string {
+            indexing: summary | index
+        }
+    }
+    onnx-model ltr_tensorflow {
+        file: files/ltr_tensorflow.onnx
+        input input:0: vespa_input
+        output dense: dense
+    }
+}
+
+
+

Add a rank profile named tensorflow that uses the TensorFlow model to rank documents. * first_phase: We use the Vespa ranking feature onnx to access the ONNX model named ltr_tensorflow and use the output dense. We apply the sum because Vespa requires the relevance score to be a scaler and the output of the ONNX model in this case is a tensor of shape (1,1). * vespa_input function: The ONNX model was trained with the features fieldMatch(text).queryCompleteness, fieldMatch(text).significance and nativeRank(text) and expects and tensor of shape (1,3) containing those features. * summary_features: Summary features allow us to specify Vespa features to be included in the output of a query. In this case, we want to access to the model inputs and output to check if the Vespa model evaluation is the same as if we use the original TensorFlow model.

+
+
from vespa.package import RankProfile, Function
+
+app_package.schema.add_rank_profile(
+    RankProfile(
+        name="tensorflow", 
+        first_phase="sum(onnx(ltr_tensorflow).dense)", 
+        functions=[
+            Function(
+                name="vespa_input", 
+                expression="tensor<float>(x[1],y[3]):[["
+                    "fieldMatch(text).queryCompleteness, "
+                    "fieldMatch(text).significance, "
+                    "nativeRank(text)"
+                "]]"
+            )
+        ],
+        summary_features=[
+            "onnx(ltr_tensorflow)", 
+            "fieldMatch(text).queryCompleteness", 
+            "fieldMatch(text).significance", 
+            "nativeRank(text)"
+        ]
+    )
+)
+
+

The rank-profile called tensorflow can be seen below:

+
+
print(app_package.schema.schema_to_text)
+
+
schema msmarco {
+    document msmarco {
+        field id type string {
+            indexing: summary | attribute
+        }
+        field text type string {
+            indexing: summary | index
+        }
+    }
+    onnx-model ltr_tensorflow {
+        file: files/ltr_tensorflow.onnx
+        input input:0: vespa_input
+        output dense: dense
+    }
+    rank-profile tensorflow {
+        function vespa_input() {
+            expression {
+                tensor<float>(x[1],y[3]):[[fieldMatch(text).queryCompleteness, fieldMatch(text).significance, nativeRank(text)]]
+            }
+        }
+        first-phase {
+            expression {
+                sum(onnx(ltr_tensorflow).dense)
+            }
+        }
+        summary-features {
+            onnx(ltr_tensorflow)
+            fieldMatch(text).queryCompleteness
+            fieldMatch(text).significance
+            nativeRank(text)
+        }
+    }
+}
+
+
+

Now that we are done with the application package definition. We can deploy the application:

+
+
from vespa.deployment import VespaDocker
+
+vespa_docker = VespaDocker()
+app = vespa_docker.deploy(application_package=app_package)
+
+
Waiting for configuration server, 0/300 seconds...
+Waiting for configuration server, 5/300 seconds...
+Waiting for application status, 0/300 seconds...
+Waiting for application status, 5/300 seconds...
+Waiting for application status, 10/300 seconds...
+Waiting for application status, 15/300 seconds...
+Waiting for application status, 20/300 seconds...
+Waiting for application status, 25/300 seconds...
+Finished deployment.
+
+
+
+
+

Feed the application

+

Once the application is running, it is time to feed msmarco passage data to it.

+
+
from learntorank.passage import PassageData
+
+dataset = PassageData.load()
+
+

We are going to use only 10 documents because our goal here is to show that Vespa returns the correct predictions from the TensorFlow model.

+
+
data = dataset.get_corpus().head(10)
+data.rename(columns={'doc_id': 'id'}, inplace=True)
+
+
+
data.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
idtext
05954248Why GameStop is excited for Dragon Age: Inquis...
17290700metaplasia definition: 1. abnormal change of o...
25465518Candice Net Worth. According to the report of ...
33100518Under the Base Closure Act, March AFB was down...
43207764There are a number of career opportunities for...
+ +
+
+
+

Feed the data to the application.

+
+
result = app.feed_df(df=data, include_id=True)
+
+
Successful documents fed: 10/10.
+Batch progress: 1/1.
+
+
+
+
+

Validate Vespa predictions

+

Get query from the small dev set to use to validate Vespa TensorFlow predictions.

+
+
query_text = dataset.get_queries(type="dev").iloc[0,1]
+query_text = query_text.replace("'", "")
+
+
+
query_text
+
+
'why say the sky is the limit'
+
+
+

The code below shows the YQL expression that will be used to select the documents to be ranked.

+
+
"select * from sources * where ({{grammar: 'any', defaultIndex: 'text'}}userInput('{}'))".format(query_text)
+
+
"select * from sources * where ({grammar: 'any', defaultIndex: 'text'}userInput('why say the sky is the limit'))"
+
+
+

The function get_vespa_prediction_and_features will match documents using the YQL expression above and rank the documents with the rank-profile tensorflow that we defined in the Vespa application package.

+
+
def get_vespa_prediction_and_features(query_text):
+    # Send query and extract hits
+    hits = app.query(
+                body={
+                    "yql": "select * from sources * where ({{'grammar': 'any', 'defaultIndex': 'text'}}userInput('{}'));".format(query_text),
+                    "ranking": "tensorflow"
+                }
+            ).hits
+    result =[]
+    # For each hit, extract the inputs to the model along with model predictions computed by Vespa
+    for hit in hits:
+        result.append({
+            "fieldMatch(text).queryCompleteness": hit["fields"]["summaryfeatures"]["fieldMatch(text).queryCompleteness"],
+            "fieldMatch(text).significance": hit["fields"]["summaryfeatures"]["fieldMatch(text).significance"],
+            "nativeRank(text)": hit["fields"]["summaryfeatures"]["nativeRank(text)"],
+            "vespa_prediction": hit["relevance"],             
+        })
+    return pd.DataFrame.from_records(result)
+
+

Inputs and vespa predictions:

+
+
predictions = get_vespa_prediction_and_features(query_text=query_text)
+predictions
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
fieldMatch(text).queryCompletenessfieldMatch(text).significancenativeRank(text)vespa_prediction
00.2857140.1997990.0618530.360788
10.5714290.4156870.086940-0.128510
20.4285710.3020710.065154-0.240481
30.4285710.3020710.050600-0.670632
40.4285710.3020710.049802-0.694231
50.2857140.1997990.025552-0.712175
60.4285710.3020710.045398-0.824390
+ +
+
+
+

Compute predictions from the TensorFlow model simpler_model directly:

+
+
predictions["tf_prediction"] = predictions[
+    ["fieldMatch(text).queryCompleteness", "fieldMatch(text).significance", "nativeRank(text)"]
+].apply(lambda x: simpler_model.predict([x.tolist()])[0][0], axis=1)
+
+
1/1 [==============================] - 0s 71ms/step
+1/1 [==============================] - 0s 28ms/step
+1/1 [==============================] - 0s 29ms/step
+1/1 [==============================] - 0s 29ms/step
+1/1 [==============================] - 0s 28ms/step
+1/1 [==============================] - 0s 26ms/step
+1/1 [==============================] - 0s 26ms/step
+
+
+
+
predictions
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
fieldMatch(text).queryCompletenessfieldMatch(text).significancenativeRank(text)vespa_predictiontf_prediction
00.2857140.1997990.0618530.3607880.360788
10.5714290.4156870.086940-0.128510-0.128510
20.4285710.3020710.065154-0.240481-0.240481
30.4285710.3020710.050600-0.670632-0.670632
40.4285710.3020710.049802-0.694231-0.694231
50.2857140.1997990.025552-0.712175-0.712176
60.4285710.3020710.045398-0.824390-0.824390
+ +
+
+
+

Check that the predictions from the model deployed in Vespa are (almost) equal to the predictions obtained directly from the model.

+
+
from numpy.testing import assert_almost_equal
+
+assert_almost_equal(predictions["vespa_prediction"].tolist(), predictions["tf_prediction"].tolist(), 5)
+
+
+
+

Clean environment

+
+
import shutil
+
+shutil.rmtree("simpler_keras_model") 
+vespa_docker.container.stop(timeout=600)
+vespa_docker.container.remove()
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/passage_dataset.html b/passage_dataset.html new file mode 100644 index 0000000..7b23769 --- /dev/null +++ b/passage_dataset.html @@ -0,0 +1,892 @@ + + + + + + + + + +Vespa Data Science - Dataset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Dataset

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

For the passage ranking use case, we will use the MS MARCO passage dataset 1 through the ir_datasets library. Besides being convenient, ir_datasets solves encoding errors in the original dataset source files.

+
+
import ir_datasets
+import pandas as pd
+
+
+

Data Exploration

+
+

Document corpus

+

Start by loading the data. The dataset will be downloaded once and cached on disk for future use, so it takes a while the first time the command below is run.

+
+
passage_corpus = ir_datasets.load("msmarco-passage")
+
+

Number of passages in the document corpus:

+
+
passage_corpus.docs_count()
+
+
8841823
+
+
+

Sample a few passages of the document corpus.

+
+
pd.DataFrame(passage_corpus.docs_iter()[0:5])
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
doc_idtext
00The presence of communication amid scientific ...
11The Manhattan Project and its atomic bomb help...
22Essay on The Manhattan Project - The Manhattan...
33The Manhattan Project was the name for a proje...
44versions of each volume as well as complementa...
+ +
+
+
+
+
+

Training data

+

Load the training data. We use the judged version that only include queries with at least one relevance judgement.

+
+
passage_train = ir_datasets.load("msmarco-passage/train/judged")
+
+
+

Relevant documents

+

Number of relevant judgements:

+
+
passage_train.qrels_count()
+
+
532761
+
+
+

For each query id, there is a dict of relevant documents containing the document id as key and the relevance score as value.

+
+
from learntorank.passage import sample_dict_items
+
+train_qrels_dict = passage_train.qrels_dict()
+sample_dict_items(train_qrels_dict, 5)
+
+
{'1038069': {'2293922': 1},
+ '700425': {'4351261': 1},
+ '926242': {'3500124': 1},
+ '690553': {'2877918': 1},
+ '411317': {'2230220': 1}}
+
+
+

It is interesting to check what is the range of values of the relevance score. The code below shows that the only score available is 1, indicating that the particular document id is relevant to the query id.

+
+
set([score 
+     for relevant in train_qrels_dict.values() 
+     for score in relevant.values()]
+   )
+
+
{1}
+
+
+
+
+

Queries

+

Number of training queries:

+
+
passage_train.queries_count()
+
+
502939
+
+
+

The number of queries differs from the number of relevant documents because some of the queries have more than one relevant document associated with it.

+

Each query contains a query id and a query text.

+
+
training_queries = pd.DataFrame(passage_train.queries_iter())
+training_queries.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query_idtext
0121352define extreme
1634306what does chattel mean on credit history
2920825what was the great leap forward brainly
3510633tattoo fixers how much does it cost
4737889what is decentralization process.
+ +
+
+
+
+
+
+

Development data

+

Similarly to the training data, we can load the judged development data and take a look at the queries and relevance judgements.

+
+
passage_dev = ir_datasets.load("msmarco-passage/dev/judged")
+
+
+

Relevant documents

+

Number of relevant judgements:

+
+
passage_dev.qrels_count()
+
+
59273
+
+
+

For each query id, there is a dict of relevant documents containing the document id as key and the relevance score as value.

+
+
dev_qrels_dict = passage_dev.qrels_dict()
+sample_dict_items(dev_qrels_dict, 5)
+
+
{'255': {'7629892': 1},
+ '611327': {'7610137': 1},
+ '584695': {'7408281': 1},
+ '300246': {'7814106': 1, '7814107': 1},
+ '739094': {'7640560': 1}}
+
+
+
+
+

Queries

+

Number of dev queries:

+
+
passage_dev.queries_count()
+
+
55578
+
+
+

Each query contains a query id and a query text.

+
+
dev_queries = pd.DataFrame(passage_dev.queries_iter())
+dev_queries.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query_idtext
01048578cost of endless pools/swim spa
11048579what is pcnt
21048582what is paysky
31048583what is paydata
41048585what is paula deen's brother
+ +
+
+
+
+
+
+
+

Data Manipulation

+
+

Sample data

+

Given the large amount of data, it is useful to properly sample data when prototyping, which can be done with the sample_data function. This might take same time in case the full dataset needs to be downloaded for the first time.

+
+
from learntorank.passage import sample_data
+
+passage_sample = sample_data(n_relevant=100, n_irrelevant=800)
+
+
+
passage_sample
+
+
PassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)
+
+
+
+

Save

+

We can save the sampled data to disk to avoid regenerating it everytime we need to use it.

+
+
passage_sample.save("sample.json")
+
+
+
+

Load

+

Load the data back when needed with PassageData.load:

+
+
from learntorank.passage import PassageData
+
+loaded_sample = PassageData.load(file_path="sample.json")
+
+ + +
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/passage_uncertainty_evaluation.html b/passage_uncertainty_evaluation.html new file mode 100644 index 0000000..1bd91ee --- /dev/null +++ b/passage_uncertainty_evaluation.html @@ -0,0 +1,1220 @@ + + + + + + + + + + +Vespa Data Science - IR evaluation metrics with uncertainty estimates + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

IR evaluation metrics with uncertainty estimates

+
+ +
+
+ Compare different metrics and their uncertainty in the passage ranking dataset. +
+
+ + +
+ + + + +
+ + +
+ + +

When working with search engine apps, be it a text search or a recommendation system, part of the job is doing experiments around components such as ranking functions and deciding which experiments deliver the best result.

+

This tutorial builds a text search app with Vespa, feeds a sample of the passage ranking dataset to the app, and evaluates two ranking functions across three different metrics. In addition to return point estimates of the evaluation metrics, we compute confidence intervals as illustrated in the plot below. Measuring uncertainty around the metric estimates gives us a better sense of how significant is the impact of our changes in the application.

+

+

The code and the data used in this end-to-end tutorial are available and can be reproduced in a Jupyter Notebook.

+
+

Create the Vespa application package

+

Create a Vespa application package to perform passage ranking experiments using the create_basic_search_package.

+
+
from learntorank.passage import create_basic_search_package
+
+app_package = create_basic_search_package()
+
+

We can inspect how the Vespa search definition file looks like:

+
+
print(app_package.schema.schema_to_text)
+
+
schema PassageRanking {
+    document PassageRanking {
+        field doc_id type string {
+            indexing: attribute | summary
+        }
+        field text type string {
+            indexing: index | summary
+            index: enable-bm25
+        }
+    }
+    fieldset default {
+        fields: text
+    }
+    rank-profile bm25 {
+        first-phase {
+            expression: bm25(text)
+        }
+        summary-features {
+            bm25(text)
+        }
+    }
+    rank-profile native_rank {
+        first-phase {
+            expression: nativeRank(text)
+        }
+    }
+}
+
+
+

In this tutorial, we are going to compare two ranking functions. One is based on NativeRank, and the other is based on BM25.

+
+
+

Deploy the application

+

Deploy the application package in a Docker container for local development. Alternatively, it is possible to deploy the application package to Vespa Cloud.

+
+
from vespa.deployment import VespaDocker
+
+vespa_docker = VespaDocker()
+app = vespa_docker.deploy(application_package=app_package)
+
+
Waiting for configuration server, 0/300 seconds...
+Waiting for configuration server, 5/300 seconds...
+Waiting for application status, 0/300 seconds...
+Waiting for application status, 5/300 seconds...
+Waiting for application status, 10/300 seconds...
+Waiting for application status, 15/300 seconds...
+Waiting for application status, 20/300 seconds...
+Finished deployment.
+
+
+

Once the deployment is finished, we can interact with the deployed application through the app variable.

+
+
+

Get sample data

+

We can load passage ranking sample data with PassageData.load. By default, it will download pre-generated sample data.

+
+
from learntorank.passage import PassageData
+
+data = PassageData.load()
+
+
+
data
+
+
PassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)
+
+
+
+
data.summary
+
+
Number of documents: 1000
+Number of train queries: 100
+Number of train relevance judgments: 100
+Number of dev queries: 100
+Number of dev relevance judgments: 100
+
+
+
+
+

Feed the application

+

Get the document corpus in a DataFrame format.

+
+
corpus_df = data.get_corpus()
+corpus_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
doc_idtext
05954248Why GameStop is excited for Dragon Age: Inquis...
17290700metaplasia definition: 1. abnormal change of o...
25465518Candice Net Worth. According to the report of ...
33100518Under the Base Closure Act, March AFB was down...
43207764There are a number of career opportunities for...
+ +
+
+
+

Feed the data to the deployed application.

+
+
responses = app.feed_df(df=corpus_df, include_id=True, id_field="doc_id")
+
+
Successful documents fed: 1000/1000.
+Batch progress: 1/1.
+
+
+

We can also check the number of successfully fed documents through the responses status code:

+
+
sum([response.status_code == 200 for response in responses])
+
+
1000
+
+
+
+
+

Query the application

+

Get the dev set queries in a DataFrame format.

+
+
dev_queries_df = data.get_queries(type="dev")
+dev_queries_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query_idquery
01101971why say the sky is the limit
1712898what is an cvc in radiology
2154469dmv california how long does it take to get id
3930015what's an epigraph
4860085what is va tax
+ +
+
+
+

Get the first query text to use as an example when querying our passage search application.

+
+
sample_query = dev_queries_df.loc[0, "query"]
+sample_query
+
+
'why say the sky is the limit'
+
+
+
+

Query with QueryModel

+

Create the bm25 QueryModel, which uses Vespa’s weakAnd operator to match documents relevant to the query and use the bm25 rank-profile that we defined in the application package above to rank the documents.

+
+
from learntorank.query import QueryModel, WeakAnd, Ranking
+
+bm25_query_model = QueryModel(
+    name="bm25", 
+    match_phase=WeakAnd(hits=100), 
+    ranking=Ranking(name="bm25")
+)
+
+

Once a QueryModel is specified, we can use it to query our application.

+
+
from learntorank.query import send_query
+from pprint import pprint
+
+response = send_query(
+    app=app,
+    query=sample_query, 
+    query_model=bm25_query_model
+)
+pprint(response.hits[0:2])
+
+
[{'fields': {'doc_id': '7407715',
+             'documentid': 'id:PassageRanking:PassageRanking::7407715',
+             'sddocname': 'PassageRanking',
+             'summaryfeatures': {'bm25(text)': 11.979235042476953,
+                                 'vespa.summaryFeatures.cached': 0.0},
+             'text': 'The Sky is the Limit also known as TSITL is a global '
+                     'effort designed to influence, motivate and inspire '
+                     'people all over the world to achieve their goals and '
+                     'dreams in life. TSITL’s collaborative community on '
+                     'social media provides you with a vast archive of '
+                     'motivational pictures/quotes/videos.'},
+  'id': 'id:PassageRanking:PassageRanking::7407715',
+  'relevance': 11.979235042476953,
+  'source': 'PassageRanking_content'},
+ {'fields': {'doc_id': '84721',
+             'documentid': 'id:PassageRanking:PassageRanking::84721',
+             'sddocname': 'PassageRanking',
+             'summaryfeatures': {'bm25(text)': 11.310323797415357,
+                                 'vespa.summaryFeatures.cached': 0.0},
+             'text': 'Sky Customer Service 0870 280 2564. Use the Sky contact '
+                     'number to get in contact with the Sky customer services '
+                     'team to speak to a representative about your Sky TV, Sky '
+                     'Internet or Sky telephone services. The Sky customer '
+                     'Services team is operational between 8:30am and 11:30pm '
+                     'seven days a week.'},
+  'id': 'id:PassageRanking:PassageRanking::84721',
+  'relevance': 11.310323797415357,
+  'source': 'PassageRanking_content'}]
+
+
+
+
+

Query with Vespa Query Language

+

We can also translate the query created with the QueryModel into the Vespa Query Language (YQL) by setting debug_request=True:

+
+
response = send_query(
+    app=app,
+    query = sample_query, 
+    query_model=bm25_query_model, 
+    debug_request=True
+)
+yql_body = response.request_body
+pprint(yql_body)
+
+
{'ranking': {'listFeatures': 'false', 'profile': 'bm25'},
+ 'yql': 'select * from sources * where ({targetHits: 100}weakAnd(default '
+        'contains "why", default contains "say", default contains "the", '
+        'default contains "sky", default contains "is", default contains '
+        '"the", default contains "limit"));'}
+
+
+

We can use Vespa YQL directly via the body parameter:

+
+
yql_response = send_query(app=app, body=yql_body)
+pprint(yql_response.hits[0:2])
+
+
[{'fields': {'doc_id': '7407715',
+             'documentid': 'id:PassageRanking:PassageRanking::7407715',
+             'sddocname': 'PassageRanking',
+             'summaryfeatures': {'bm25(text)': 11.979235042476953,
+                                 'vespa.summaryFeatures.cached': 0.0},
+             'text': 'The Sky is the Limit also known as TSITL is a global '
+                     'effort designed to influence, motivate and inspire '
+                     'people all over the world to achieve their goals and '
+                     'dreams in life. TSITL’s collaborative community on '
+                     'social media provides you with a vast archive of '
+                     'motivational pictures/quotes/videos.'},
+  'id': 'id:PassageRanking:PassageRanking::7407715',
+  'relevance': 11.979235042476953,
+  'source': 'PassageRanking_content'},
+ {'fields': {'doc_id': '84721',
+             'documentid': 'id:PassageRanking:PassageRanking::84721',
+             'sddocname': 'PassageRanking',
+             'summaryfeatures': {'bm25(text)': 11.310323797415357,
+                                 'vespa.summaryFeatures.cached': 0.0},
+             'text': 'Sky Customer Service 0870 280 2564. Use the Sky contact '
+                     'number to get in contact with the Sky customer services '
+                     'team to speak to a representative about your Sky TV, Sky '
+                     'Internet or Sky telephone services. The Sky customer '
+                     'Services team is operational between 8:30am and 11:30pm '
+                     'seven days a week.'},
+  'id': 'id:PassageRanking:PassageRanking::84721',
+  'relevance': 11.310323797415357,
+  'source': 'PassageRanking_content'}]
+
+
+
+
+
+

Evaluate query models

+

In this section, we want to evaluate and compare the bm25_query_model defined above with the native_query_model defined below:

+
+
native_query_model = QueryModel(
+    name="native_rank", 
+    match_phase=WeakAnd(hits=100), 
+    ranking=Ranking(name="native_rank")
+)
+
+

We specify three metrics to evaluate the models.

+
+
from learntorank.evaluation import (
+    Recall, 
+    ReciprocalRank, 
+    NormalizedDiscountedCumulativeGain
+)
+
+metrics = [
+    Recall(at=10), 
+    ReciprocalRank(at=3), 
+    NormalizedDiscountedCumulativeGain(at=3)
+]
+
+
+

Point estimates

+

It is straightforward to obtain point estimates of the evaluation metrics for each query model being compared. In this case, we computed the mean and the standard deviation for each of the metrics.

+
+
from learntorank.evaluation import evaluate
+
+evaluation = evaluate(
+    app=app,
+    labeled_data=data.get_labels(type="dev"), 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+    aggregators=["mean", "std"]
+ )
+
+
+
evaluation
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelbm25native_rank
recall_10mean0.9358330.845833
std0.2154440.342749
reciprocal_rank_3mean0.9350000.755000
std0.2319770.394587
ndcg_3mean0.9128390.749504
std0.2422720.381792
+ +
+
+
+

Given the nature of the data distribution of the metrics described above, it is not trivial to compute a confidence interval from the mean and the standard deviation computed above. In the next section, we solve this by using bootstrap sampling on a per query metric evaluation.

+
+
+

Uncertainty estimates

+

Instead of returning aggregated point estimates, we can also compute the metrics per query by setting per_query=True. This gives us more granular information on the distribution function of the metrics.

+
+
evaluation_per_query = evaluate(
+    app=app,
+    labeled_data=data.get_labels(type="dev"), 
+    eval_metrics=metrics, 
+    query_model=[native_query_model, bm25_query_model], 
+    id_field="doc_id",
+    per_query=True
+)
+
+
+
evaluation_per_query.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modelquery_idrecall_10reciprocal_rank_3ndcg_3
0native_rank11019711.01.01.0
1native_rank7128980.00.00.0
2native_rank1544691.00.00.0
3native_rank9300151.01.01.0
4native_rank8600850.00.00.0
+ +
+
+
+

We then created a function that uses the evaluation per query data and computes uncertainty estimates via bootstrap sampling.

+
+
from learntorank.stats import compute_evaluation_estimates
+
+estimates = compute_evaluation_estimates(
+    df = evaluation_per_query
+)
+
+
+
estimates
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
metricmodellowmedianhigh
0ndcg_3bm250.8652790.9148630.958766
1ndcg_3native_rank0.6752200.7524000.820318
2recall_10bm250.8916670.9358330.974187
3recall_10native_rank0.7783120.8479170.910000
4reciprocal_rank_3bm250.8900000.9350000.975000
5reciprocal_rank_3native_rank0.6783330.7583330.831667
+ +
+
+
+

We can then create plots based on this data to make it easier to judge the magnitude of the differences between ranking functions.

+
+
from plotnine import *
+
+print((ggplot(estimates) + 
+ geom_point(aes("model", "median")) + 
+ geom_errorbar(aes(x="model", ymin="low",ymax="high")) + 
+ facet_wrap("metric") + labs(y="Metric value")
+))
+
+

+
+
+
+
+
+
+
+
+

Cleanup the environment

+
+
vespa_docker.container.stop(timeout=600)
+vespa_docker.container.remove()
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/resources/passage/passage_uncertainty.png b/resources/passage/passage_uncertainty.png new file mode 100644 index 0000000..c6824d6 Binary files /dev/null and b/resources/passage/passage_uncertainty.png differ diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..3315113 --- /dev/null +++ b/robots.txt @@ -0,0 +1 @@ +Sitemap: https://thigm85.github.io/learntorank/sitemap.xml diff --git a/search.json b/search.json new file mode 100644 index 0000000..bb57a6f --- /dev/null +++ b/search.json @@ -0,0 +1,646 @@ +[ + { + "objectID": "module_ml.html", + "href": "module_ml.html", + "title": "ml", + "section": "", + "text": "source\n\n\n\n Task (model_id:str)\n\nBase class for ML Tasks.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nmodel_id\nstr\nId used to identify the model on Vespa applications.\n\n\n\n\nsource\n\n\n\n\n TextTask (model_id:str, model:str, tokenizer:Optional[str]=None,\n output_file:<class'IO'>=<_io.StringIO object at\n 0x7f8fe5e67160>)\n\nBase class for Tasks involving text inputs.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nmodel_id\nstr\n\nId used to identify the model on Vespa applications.\n\n\nmodel\nstr\n\nId of the model as used by the model hub.\n\n\ntokenizer\ntyping.Optional[str]\nNone\nId of the tokenizer as used by the model hub.\n\n\noutput_file\nIO\n<_io.StringIO object at 0x7f8fe5e67160>\nOutput file to write output messages.\n\n\n\n\nsource\n\n\n\n\n TextTask.export_to_onnx (output_path:str)\n\nExport a model to ONNX\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\noutput_path\nstr\nRelative output path for the onnx model, should end in ‘.onnx’\n\n\nReturns\nNone\n\n\n\n\n\nsource\n\n\n\n\n TextTask.predict (text:str)\n\nPredict using a local instance of the model\n\n\n\n\nType\nDetails\n\n\n\n\ntext\nstr\ntext input for the task.\n\n\nReturns\ntyping.List\nPredictions.\n\n\n\n\nsource\n\n\n\n\n SequenceClassification (model_id:str, model:str,\n tokenizer:Optional[str]=None,\n output_file:<class'IO'>=<_io.StringIO object at\n 0x7f8f7b2e3b80>)\n\nSequence Classification task.\nIt takes a text input and returns an array of floats depending on which model is used to solve the task.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nmodel_id\nstr\n\nId used to identify the model on Vespa applications.\n\n\nmodel\nstr\n\nId of the model as used by the model hub. Alternatively, it can also be the path to the folder containing the model files, as long as the model config is also there.\n\n\ntokenizer\ntyping.Optional[str]\nNone\nId of the tokenizer as used by the model hub. Alternatively, it can also be the path to the folder containing the tokenizer files, as long as the model config is also there.\n\n\noutput_file\nIO\n<_io.StringIO object at 0x7f8f7b2e3b80>\nOutput file to write output messages." + }, + { + "objectID": "module_ml.html#ml-tasks-involving-text-inputs", + "href": "module_ml.html#ml-tasks-involving-text-inputs", + "title": "ml", + "section": "", + "text": "source\n\n\n\n Task (model_id:str)\n\nBase class for ML Tasks.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nmodel_id\nstr\nId used to identify the model on Vespa applications.\n\n\n\n\nsource\n\n\n\n\n TextTask (model_id:str, model:str, tokenizer:Optional[str]=None,\n output_file:<class'IO'>=<_io.StringIO object at\n 0x7f8fe5e67160>)\n\nBase class for Tasks involving text inputs.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nmodel_id\nstr\n\nId used to identify the model on Vespa applications.\n\n\nmodel\nstr\n\nId of the model as used by the model hub.\n\n\ntokenizer\ntyping.Optional[str]\nNone\nId of the tokenizer as used by the model hub.\n\n\noutput_file\nIO\n<_io.StringIO object at 0x7f8fe5e67160>\nOutput file to write output messages.\n\n\n\n\nsource\n\n\n\n\n TextTask.export_to_onnx (output_path:str)\n\nExport a model to ONNX\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\noutput_path\nstr\nRelative output path for the onnx model, should end in ‘.onnx’\n\n\nReturns\nNone\n\n\n\n\n\nsource\n\n\n\n\n TextTask.predict (text:str)\n\nPredict using a local instance of the model\n\n\n\n\nType\nDetails\n\n\n\n\ntext\nstr\ntext input for the task.\n\n\nReturns\ntyping.List\nPredictions.\n\n\n\n\nsource\n\n\n\n\n SequenceClassification (model_id:str, model:str,\n tokenizer:Optional[str]=None,\n output_file:<class'IO'>=<_io.StringIO object at\n 0x7f8f7b2e3b80>)\n\nSequence Classification task.\nIt takes a text input and returns an array of floats depending on which model is used to solve the task.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nmodel_id\nstr\n\nId used to identify the model on Vespa applications.\n\n\nmodel\nstr\n\nId of the model as used by the model hub. Alternatively, it can also be the path to the folder containing the model files, as long as the model config is also there.\n\n\ntokenizer\ntyping.Optional[str]\nNone\nId of the tokenizer as used by the model hub. Alternatively, it can also be the path to the folder containing the tokenizer files, as long as the model config is also there.\n\n\noutput_file\nIO\n<_io.StringIO object at 0x7f8f7b2e3b80>\nOutput file to write output messages." + }, + { + "objectID": "module_ml.html#model-config-for-vespa-applications", + "href": "module_ml.html#model-config-for-vespa-applications", + "title": "ml", + "section": "Model config for Vespa applications", + "text": "Model config for Vespa applications\n\nsource\n\nModelConfig\n\n ModelConfig (model_id)\n\nBase model configuration for Vespa applications.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nmodel_id\n\nUnique model id to represent the model within a Vespa application.\n\n\nReturns\nNone\n\n\n\n\n\nsource\n\n\nBertModelConfig\n\n BertModelConfig (model_id:str, query_input_size:int, doc_input_size:int,\n tokenizer:Union[str,os.PathLike],\n model:Union[str,os.PathLike,NoneType]=None)\n\nBERT model configuration for Vespa applications.\n\n\n\nbert_config = BertModelConfig( … model_id=“pretrained_bert_tiny”, … query_input_size=32, … doc_input_size=96, … tokenizer=“google/bert_uncased_L-2_H-128_A-2”, … model=“google/bert_uncased_L-2_H-128_A-2”, … ) # doctest: +SKIP BertModelConfig(‘pretrained_bert_tiny’, 32, 96, ‘google/bert_uncased_L-2_H-128_A-2’, ‘google/bert_uncased_L-2_H-128_A-2’)\n\n\n\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nmodel_id\nstr\n\nUnique model id to represent the model within a Vespa application.\n\n\nquery_input_size\nint\n\nThe size of the input vector dedicated to the query text.\n\n\ndoc_input_size\nint\n\nThe size of the input vector dedicated to the document text.\n\n\ntokenizer\ntyping.Union[str, os.PathLike]\n\nThe name or a path to a saved BERT model tokenizer from the transformers library.\n\n\nmodel\ntyping.Union[str, os.PathLike, NoneType]\nNone\nThe name or a path to a saved model that is compatible with the tokenizer. The model is optional at construction since you might want to train it first. You must add a model via :func:add_model before deploying a Vespa application that uses this class.\n\n\nReturns\nNone\n\n\n\n\n\n\n#bert_config = BertModelConfig(\n# model_id=\"pretrained_bert_tiny\",\n# query_input_size=32,\n# doc_input_size=96,\n# tokenizer=\"google/bert_uncased_L-2_H-128_A-2\"\n#)\n\n\n#bert_config = BertModelConfig(\n# model_id=\"pretrained_bert_tiny\",\n# query_input_size=32,\n# doc_input_size=96,\n# tokenizer=\"google/bert_uncased_L-2_H-128_A-2\",\n# model=\"google/bert_uncased_L-2_H-128_A-2\",\n#)\n\n\nsource\n\n\nBertModelConfig.predict\n\n BertModelConfig.predict (queries, docs)\n\nPredict (forward pass) given queries and docs texts\n\n\n\n\nType\nDetails\n\n\n\n\nqueries\n\nA List of query texts.\n\n\ndocs\n\nA List of document texts.\n\n\nReturns\ntyping.List\nLogits\n\n\n\n\nsource\n\n\nBertModelConfig.add_model\n\n BertModelConfig.add_model (model:Union[str,os.PathLike])\n\nAdd a BERT model\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nmodel\ntyping.Union[str, os.PathLike]\nThe name or a path to a saved model that is compatible with the tokenizer.\n\n\nReturns\nNone\n\n\n\n\n\nsource\n\n\nBertModelConfig.doc_fields\n\n BertModelConfig.doc_fields (text:str)\n\nGenerate document fields related to the model that needs to be fed to Vespa.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\ntext\nstr\nThe text related to the document to be used as input to the bert model\n\n\nReturns\ntyping.Dict\nDict with key and values as expected by Vespa.\n\n\n\n\nsource\n\n\nBertModelConfig.query_tensor_mapping\n\n BertModelConfig.query_tensor_mapping (text:str)\n\nMaps query text to a tensor expected by Vespa at run time.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\ntext\nstr\nQuery text to be used as input to the BERT model.\n\n\nReturns\ntyping.List[float]\nInput ids expected by Vespa.\n\n\n\n\nsource\n\n\nBertModelConfig.create_encodings\n\n BertModelConfig.create_encodings (queries:List[str], docs:List[str],\n return_tensors=False)\n\nCreate BERT model encodings.\nCreate BERT encodings following the same pattern used during Vespa serving. Useful to generate training data and ensuring training and serving compatibility.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nqueries\ntyping.List[str]\n\nQuery texts.\n\n\ndocs\ntyping.List[str]\n\nDocument texts.\n\n\nreturn_tensors\nbool\nFalse\nReturn tensors\n\n\nReturns\ntyping.Dict\n\nDict containing input_ids, token_type_ids and attention_mask encodings.\n\n\n\n\nsource\n\n\nBertModelConfig.export_to_onnx\n\n BertModelConfig.export_to_onnx (output_path:str)\n\nExport a model to ONNX\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\noutput_path\nstr\nRelative output path for the onnx model, should end in ‘.onnx’\n\n\nReturns\nNone\n\n\n\n\n\nsource\n\n\nBertModelConfig.onnx_model\n\n BertModelConfig.onnx_model ()\n\n\nsource\n\n\nBertModelConfig.query_profile_type_fields\n\n BertModelConfig.query_profile_type_fields ()\n\n\nsource\n\n\nBertModelConfig.document_fields\n\n BertModelConfig.document_fields (document_field_indexing)\n\n\nsource\n\n\nBertModelConfig.rank_profile\n\n BertModelConfig.rank_profile (include_model_summary_features, **kwargs)" + }, + { + "objectID": "module_ml.html#model-server", + "href": "module_ml.html#model-server", + "title": "ml", + "section": "Model Server", + "text": "Model Server\n\nsource\n\nModelServer\n\n ModelServer (name:str, tasks:Optional[List[__main__.Task]]=None)\n\nCreate a Vespa stateless model evaluation server.\nA Vespa stateless model evaluation server is a simplified Vespa application without content clusters.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nname\nstr\n\nApplication name.\n\n\ntasks\ntyping.Optional[typing.List[main.Task]]\nNone\nList of tasks to be served." + }, + { + "objectID": "module_ml.html#add-ranking-model", + "href": "module_ml.html#add-ranking-model", + "title": "ml", + "section": "Add ranking model", + "text": "Add ranking model\n\nsource\n\nadd_ranking_model\n\n add_ranking_model (app_package:vespa.package.ApplicationPackage,\n model_config:__main__.ModelConfig, schema=None,\n include_model_summary_features=False,\n document_field_indexing=None, **kwargs)\n\nAdd ranking profile based on a specific model config.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp_package\nApplicationPackage\n\nApplication package to include ranking model\n\n\nmodel_config\nModelConfig\n\nModel config instance specifying the model to be used on the RankProfile.\n\n\nschema\nNoneType\nNone\nName of the schema to add model ranking to.\n\n\ninclude_model_summary_features\nbool\nFalse\nTrue to include model specific summary features, such as inputs and outputs that are useful for debugging. Default to False as this requires an extra model evaluation when fetching summary features.\n\n\ndocument_field_indexing\nNoneType\nNone\nList of indexing attributes for the document fields required by the ranking model.\n\n\nkwargs\n\n\n\n\n\nReturns\nNone\n\nFurther arguments to be passed to RankProfile." + }, + { + "objectID": "module_passage.html", + "href": "module_passage.html", + "title": "passage", + "section": "", + "text": "Code related to the manipulation of passage ranking data.\n\nsource\n\n\n\n sample_dict_items (d:Dict, n:int)\n\nSample items from a dict.\n\n\n\n\nType\nDetails\n\n\n\n\nd\ntyping.Dict\ndict to be samples from.\n\n\nn\nint\nNumber of samples\n\n\nReturns\ntyping.Dict\ndict with sampled values\n\n\n\nUsage:\n\nd = {\"a\": 1, \"b\":2, \"c\":3}\n\n\nsample_dict_items(d, 1)\n\n{'a': 1}\n\n\n\nsample_dict_items(d, 2)\n\n{'c': 3, 'b': 2}\n\n\n\nsample_dict_items(d, 3)\n\n{'a': 1, 'c': 3, 'b': 2}\n\n\nReturn full dict in case number of samples is higher than length of the dict:\n\nsample_dict_items(d, 4)\n\n{'a': 1, 'c': 3, 'b': 2}\n\n\n\nsource\n\n\n\n\n save_data (corpus:Dict, train_qrels:Dict, train_queries:Dict,\n dev_qrels:Dict, dev_queries:Dict,\n file_path:str='passage_sample.json')\n\nSave data to disk.\nThe main goal is to save sample data to disk.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ncorpus\ntyping.Dict\n\nDocument corpus, see usage example below.\n\n\ntrain_qrels\ntyping.Dict\n\nTraining relevance scores, see usage example below.\n\n\ntrain_queries\ntyping.Dict\n\nTraining queries, see usage example below.\n\n\ndev_qrels\ntyping.Dict\n\nDevelopment relevance scores, see usage example below.\n\n\ndev_queries\ntyping.Dict\n\nDevelopment queries, see usage example below.\n\n\nfile_path\nstr\npassage_sample.json\nValid JSON file path.\n\n\nReturns\nNone\n\nSide-effect: data is saved to file_path.\n\n\n\nUsage:\n\ncorpus = {\n \"0\": \"sentence 0\", \n \"1\": \"sentence 1\", \n \"2\": \"sentence 2\", \n \"3\": \"sentence 3\"\n}\ntrain_queries = {\n \"10\": \"train query 10\",\n \"11\": \"train query 11\"\n}\ntrain_qrels = {\n \"10\": {\"0\": 1},\n \"11\": {\"2\": 1}\n}\ndev_queries = {\n \"20\": \"train query 20\",\n \"21\": \"train query 21\"\n}\ndev_qrels = {\n \"20\": {\"1\": 1},\n \"21\": {\"3\": 1}\n}\n\n\nsave_data(\n corpus, \n train_qrels, train_queries, \n dev_qrels, dev_queries, \n file_path=\"passage_sample.json\"\n)\n\n\nsource\n\n\n\n\n load_data (file_path:Optional[str]=None)\n\nLoad data.\nThe main goal is to load sample data from disk. If a file_path is not provided, a pre-generated data sample will be downloaded.\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nfile_path\ntyping.Optional[str]\nNone\nvalid JSON file path contain data saved by save_data. If None, a pre-generated sample will be downloaded.\n\n\nReturns\ntyping.Dict\n\nSee usage example below for expected format.\n\n\n\nUsage:\n\nWith file_path:\n\n\ndata = load_data(\"passage_sample.json\")\n\n\ndata\n\n{'corpus': {'0': 'sentence 0',\n '1': 'sentence 1',\n '2': 'sentence 2',\n '3': 'sentence 3'},\n 'train_qrels': {'10': {'0': 1}, '11': {'2': 1}},\n 'train_queries': {'10': 'train query 10', '11': 'train query 11'},\n 'dev_qrels': {'20': {'1': 1}, '21': {'3': 1}},\n 'dev_queries': {'20': 'train query 20', '21': 'train query 21'}}\n\n\n\nWithout file_path specified, a pre-generated sample data will be downloaded:\n\n\ndata = load_data()\n\n\ndata.keys()\n\ndict_keys(['corpus', 'train_qrels', 'train_queries', 'dev_qrels', 'dev_queries'])\n\n\n\nlen(data[\"corpus\"])\n\n1000\n\n\n\nsource\n\n\n\n\n PassageData (corpus:Optional[Dict]=None, train_qrels:Optional[Dict]=None,\n train_queries:Optional[Dict]=None,\n dev_qrels:Optional[Dict]=None,\n dev_queries:Optional[Dict]=None)\n\nContainer for passage data\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ncorpus\ntyping.Optional[typing.Dict]\nNone\nDocument corpus, see usage example below.\n\n\ntrain_qrels\ntyping.Optional[typing.Dict]\nNone\nTraining relevance scores, see usage example below.\n\n\ntrain_queries\ntyping.Optional[typing.Dict]\nNone\nTraining queries, see usage example below.\n\n\ndev_qrels\ntyping.Optional[typing.Dict]\nNone\nDevelopment relevance scores, see usage example below.\n\n\ndev_queries\ntyping.Optional[typing.Dict]\nNone\nDevelopment queries, see usage example below.\n\n\n\nUsage:\n\ncorpus = {\n \"0\": \"sentence 0\", \n \"1\": \"sentence 1\", \n \"2\": \"sentence 2\", \n \"3\": \"sentence 3\"\n}\ntrain_queries = {\n \"10\": \"train query 10\",\n \"11\": \"train query 11\"\n}\ntrain_qrels = {\n \"10\": {\"0\": 1},\n \"11\": {\"2\": 1}\n}\ndev_queries = {\n \"20\": \"train query 20\",\n \"21\": \"train query 21\"\n}\ndev_qrels = {\n \"20\": {\"1\": 1},\n \"21\": {\"3\": 1}\n}\n\n\npassage_data = PassageData(\n corpus=corpus, \n train_queries = train_queries, \n train_qrels=train_qrels,\n dev_queries = dev_queries,\n dev_qrels = dev_qrels\n)\n\n\npassage_data\n\nPassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)\n\n\n\nsource\n\n\n\n\n PassageData.save (file_path:str='passage_sample.json')\n\n\npassage_data.save()\n\n\nsource\n\n\n\n\n PassageData.load (file_path:Optional[str]=None)\n\nLoad passage data from disk.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nfile_path\ntyping.Optional[str]\nNone\nvalid JSON file path contain data saved by save_data. If None, a pre-generated sample will be downloaded.\n\n\nReturns\nPassageData\n\n\n\n\n\n\ndata = PassageData.load(file_path=\"passage_sample.json\")\n\n\ndata\n\nPassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)\n\n\n\nsource\n\n\n\n\n PassageData.summary ()\n\nSummary of the size of the dataset components.\n\ndata.summary\n\nNumber of documents: 4\nNumber of train queries: 2\nNumber of train relevance judgments: 2\nNumber of dev queries: 2\nNumber of dev relevance judgments: 2\n\n\n\nsource\n\n\n\n\n PassageData.get_corpus ()\n\n\npassage_data.get_corpus()\n\n\n\n\n\n\n\n\ndoc_id\ntext\n\n\n\n\n0\n0\nsentence 0\n\n\n1\n1\nsentence 1\n\n\n2\n2\nsentence 2\n\n\n3\n3\nsentence 3\n\n\n\n\n\n\n\n\nsource\n\n\n\n\n PassageData.get_queries (type:str)\n\nGet query data.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\ntype\nstr\nEither ‘train’ or ‘dev’.\n\n\nReturns\nDataFrame\nDataFrame conaining ‘query_id’ and ‘query’.\n\n\n\n\npassage_data.get_queries(type=\"train\")\n\n\n\n\n\n\n\n\nquery_id\nquery\n\n\n\n\n0\n10\ntrain query 10\n\n\n1\n11\ntrain query 11\n\n\n\n\n\n\n\n\npassage_data.get_queries(type=\"dev\")\n\n\n\n\n\n\n\n\nquery_id\nquery\n\n\n\n\n0\n20\ntrain query 20\n\n\n1\n21\ntrain query 21\n\n\n\n\n\n\n\n\nsource\n\n\n\n\n PassageData.get_labels (type:str)\n\nGet labeled data\n\n\n\n\nType\nDetails\n\n\n\n\ntype\nstr\nEither ‘train’ or ‘dev’.\n\n\nReturns\ntyping.Dict\npyvespa-formatted labeled data\n\n\n\n\npassage_data.get_labels(type=\"train\")\n\n[{'query_id': '10',\n 'query': 'train query 10',\n 'relevant_docs': [{'id': '0', 'score': 1}]},\n {'query_id': '11',\n 'query': 'train query 11',\n 'relevant_docs': [{'id': '2', 'score': 1}]}]\n\n\n\npassage_data.get_labels(type=\"dev\")\n\n[{'query_id': '20',\n 'query': 'train query 20',\n 'relevant_docs': [{'id': '1', 'score': 1}]},\n {'query_id': '21',\n 'query': 'train query 21',\n 'relevant_docs': [{'id': '3', 'score': 1}]}]\n\n\n\nsource\n\n\n\n\n sample_data (n_relevant:int, n_irrelevant:int)\n\nSample data from the passage ranking dataset.\nThe final sample contains n_relevant train relevant documents, n_relevant dev relevant documents and n_irrelevant random documents sampled from the entire corpus.\nAll the relevant sampled documents, both from train and dev sets, are guaranteed to be on the corpus_sample, which will contain 2 * n_relevant + n_irrelevant documents.\n\n\n\n\nType\nDetails\n\n\n\n\nn_relevant\nint\nThe number of relevant documents to sample.\n\n\nn_irrelevant\nint\nThe number of non-judged documents to sample.\n\n\nReturns\nPassageData\n\n\n\n\nUsage:\n\nsample = sample_data(n_relevant=1, n_irrelevant=3)\n\nThe sampled corpus is a dict containing document id as key and the passage text as value.\n\nsample.corpus\n\n{'890370': 'the map of europe gives you a clear view of the political boundaries that segregate the countries in the continent including germany uk france spain italy greece romania ukraine hungary austria sweden finland norway czech republic belgium luxembourg switzerland croatia and albaniahe map of europe gives you a clear view of the political boundaries that segregate the countries in the continent including germany uk france spain italy greece romania ukraine hungary austria sweden finland norway czech republic belgium luxembourg switzerland croatia and albania',\n '5060205': 'Setting custom HTTP headers with cURL can be done by using the CURLOPT_HTTPHEADER option, which can be set with the curl_setopt function. To add headers to your HTTP request you need to put them into a PHP Array, which you can then pass to the cul_setopt function, like demonstrated in the below example.',\n '6096573': \"The sugar in RNA is ribose, whereas the sugar in DNA is deoxyribose. The only difference between the two is that in deoxyribose, there is an oxygen missing from the 2' carbon …(there is a H there instead of an OH). This makes DNA more stable/less reactive than RNA. 1 person found this useful.\",\n '3092885': 'All three C-Ph bonds are typical of sp 3 - sp 2 carbon-carbon bonds with lengths of approximately 1.47 A, å while The-C o bond length is approximately.1 42. A å the presence of three adjacent phenyl groups confers special properties manifested in the reactivity of. the alcohol',\n '7275560': 'shortest phase of mitosis Anaphase is the shortest phase of mitosis. During anaphase the arranged chromosomes at the metaphase plate are migrate towards their respective poles. Before this migration started, chromosomes are divided into sister chromatids, by the separation of joined centromere of two sister chromatids of a chromosomes.'}\n\n\nThe size of the sampled document corpus is equal to 2 * n_relevant + n_irrelevant.\n\nlen(sample.corpus)\n\n5\n\n\nSampled queries are dict containing query id as key and query text as value.\n\nprint(sample.train_queries)\nprint(sample.dev_queries)\n\n{'899723': 'what sugar is found in rna'}\n{'994205': 'which is the shortest stage in duration'}\n\n\nSampled qrels contains one relevant document for each query.\n\nprint(sample.train_qrels)\nprint(sample.dev_qrels)\n\n{'899723': {'6096573': 1}}\n{'994205': {'7275560': 1}}\n\n\nThe following relevant documents are guaranteed to be included in the corpus_sample.\n\n\n['6096573', '7275560']" + }, + { + "objectID": "module_passage.html#data-manipulation", + "href": "module_passage.html#data-manipulation", + "title": "passage", + "section": "", + "text": "Code related to the manipulation of passage ranking data.\n\nsource\n\n\n\n sample_dict_items (d:Dict, n:int)\n\nSample items from a dict.\n\n\n\n\nType\nDetails\n\n\n\n\nd\ntyping.Dict\ndict to be samples from.\n\n\nn\nint\nNumber of samples\n\n\nReturns\ntyping.Dict\ndict with sampled values\n\n\n\nUsage:\n\nd = {\"a\": 1, \"b\":2, \"c\":3}\n\n\nsample_dict_items(d, 1)\n\n{'a': 1}\n\n\n\nsample_dict_items(d, 2)\n\n{'c': 3, 'b': 2}\n\n\n\nsample_dict_items(d, 3)\n\n{'a': 1, 'c': 3, 'b': 2}\n\n\nReturn full dict in case number of samples is higher than length of the dict:\n\nsample_dict_items(d, 4)\n\n{'a': 1, 'c': 3, 'b': 2}\n\n\n\nsource\n\n\n\n\n save_data (corpus:Dict, train_qrels:Dict, train_queries:Dict,\n dev_qrels:Dict, dev_queries:Dict,\n file_path:str='passage_sample.json')\n\nSave data to disk.\nThe main goal is to save sample data to disk.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ncorpus\ntyping.Dict\n\nDocument corpus, see usage example below.\n\n\ntrain_qrels\ntyping.Dict\n\nTraining relevance scores, see usage example below.\n\n\ntrain_queries\ntyping.Dict\n\nTraining queries, see usage example below.\n\n\ndev_qrels\ntyping.Dict\n\nDevelopment relevance scores, see usage example below.\n\n\ndev_queries\ntyping.Dict\n\nDevelopment queries, see usage example below.\n\n\nfile_path\nstr\npassage_sample.json\nValid JSON file path.\n\n\nReturns\nNone\n\nSide-effect: data is saved to file_path.\n\n\n\nUsage:\n\ncorpus = {\n \"0\": \"sentence 0\", \n \"1\": \"sentence 1\", \n \"2\": \"sentence 2\", \n \"3\": \"sentence 3\"\n}\ntrain_queries = {\n \"10\": \"train query 10\",\n \"11\": \"train query 11\"\n}\ntrain_qrels = {\n \"10\": {\"0\": 1},\n \"11\": {\"2\": 1}\n}\ndev_queries = {\n \"20\": \"train query 20\",\n \"21\": \"train query 21\"\n}\ndev_qrels = {\n \"20\": {\"1\": 1},\n \"21\": {\"3\": 1}\n}\n\n\nsave_data(\n corpus, \n train_qrels, train_queries, \n dev_qrels, dev_queries, \n file_path=\"passage_sample.json\"\n)\n\n\nsource\n\n\n\n\n load_data (file_path:Optional[str]=None)\n\nLoad data.\nThe main goal is to load sample data from disk. If a file_path is not provided, a pre-generated data sample will be downloaded.\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nfile_path\ntyping.Optional[str]\nNone\nvalid JSON file path contain data saved by save_data. If None, a pre-generated sample will be downloaded.\n\n\nReturns\ntyping.Dict\n\nSee usage example below for expected format.\n\n\n\nUsage:\n\nWith file_path:\n\n\ndata = load_data(\"passage_sample.json\")\n\n\ndata\n\n{'corpus': {'0': 'sentence 0',\n '1': 'sentence 1',\n '2': 'sentence 2',\n '3': 'sentence 3'},\n 'train_qrels': {'10': {'0': 1}, '11': {'2': 1}},\n 'train_queries': {'10': 'train query 10', '11': 'train query 11'},\n 'dev_qrels': {'20': {'1': 1}, '21': {'3': 1}},\n 'dev_queries': {'20': 'train query 20', '21': 'train query 21'}}\n\n\n\nWithout file_path specified, a pre-generated sample data will be downloaded:\n\n\ndata = load_data()\n\n\ndata.keys()\n\ndict_keys(['corpus', 'train_qrels', 'train_queries', 'dev_qrels', 'dev_queries'])\n\n\n\nlen(data[\"corpus\"])\n\n1000\n\n\n\nsource\n\n\n\n\n PassageData (corpus:Optional[Dict]=None, train_qrels:Optional[Dict]=None,\n train_queries:Optional[Dict]=None,\n dev_qrels:Optional[Dict]=None,\n dev_queries:Optional[Dict]=None)\n\nContainer for passage data\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ncorpus\ntyping.Optional[typing.Dict]\nNone\nDocument corpus, see usage example below.\n\n\ntrain_qrels\ntyping.Optional[typing.Dict]\nNone\nTraining relevance scores, see usage example below.\n\n\ntrain_queries\ntyping.Optional[typing.Dict]\nNone\nTraining queries, see usage example below.\n\n\ndev_qrels\ntyping.Optional[typing.Dict]\nNone\nDevelopment relevance scores, see usage example below.\n\n\ndev_queries\ntyping.Optional[typing.Dict]\nNone\nDevelopment queries, see usage example below.\n\n\n\nUsage:\n\ncorpus = {\n \"0\": \"sentence 0\", \n \"1\": \"sentence 1\", \n \"2\": \"sentence 2\", \n \"3\": \"sentence 3\"\n}\ntrain_queries = {\n \"10\": \"train query 10\",\n \"11\": \"train query 11\"\n}\ntrain_qrels = {\n \"10\": {\"0\": 1},\n \"11\": {\"2\": 1}\n}\ndev_queries = {\n \"20\": \"train query 20\",\n \"21\": \"train query 21\"\n}\ndev_qrels = {\n \"20\": {\"1\": 1},\n \"21\": {\"3\": 1}\n}\n\n\npassage_data = PassageData(\n corpus=corpus, \n train_queries = train_queries, \n train_qrels=train_qrels,\n dev_queries = dev_queries,\n dev_qrels = dev_qrels\n)\n\n\npassage_data\n\nPassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)\n\n\n\nsource\n\n\n\n\n PassageData.save (file_path:str='passage_sample.json')\n\n\npassage_data.save()\n\n\nsource\n\n\n\n\n PassageData.load (file_path:Optional[str]=None)\n\nLoad passage data from disk.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nfile_path\ntyping.Optional[str]\nNone\nvalid JSON file path contain data saved by save_data. If None, a pre-generated sample will be downloaded.\n\n\nReturns\nPassageData\n\n\n\n\n\n\ndata = PassageData.load(file_path=\"passage_sample.json\")\n\n\ndata\n\nPassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)\n\n\n\nsource\n\n\n\n\n PassageData.summary ()\n\nSummary of the size of the dataset components.\n\ndata.summary\n\nNumber of documents: 4\nNumber of train queries: 2\nNumber of train relevance judgments: 2\nNumber of dev queries: 2\nNumber of dev relevance judgments: 2\n\n\n\nsource\n\n\n\n\n PassageData.get_corpus ()\n\n\npassage_data.get_corpus()\n\n\n\n\n\n\n\n\ndoc_id\ntext\n\n\n\n\n0\n0\nsentence 0\n\n\n1\n1\nsentence 1\n\n\n2\n2\nsentence 2\n\n\n3\n3\nsentence 3\n\n\n\n\n\n\n\n\nsource\n\n\n\n\n PassageData.get_queries (type:str)\n\nGet query data.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\ntype\nstr\nEither ‘train’ or ‘dev’.\n\n\nReturns\nDataFrame\nDataFrame conaining ‘query_id’ and ‘query’.\n\n\n\n\npassage_data.get_queries(type=\"train\")\n\n\n\n\n\n\n\n\nquery_id\nquery\n\n\n\n\n0\n10\ntrain query 10\n\n\n1\n11\ntrain query 11\n\n\n\n\n\n\n\n\npassage_data.get_queries(type=\"dev\")\n\n\n\n\n\n\n\n\nquery_id\nquery\n\n\n\n\n0\n20\ntrain query 20\n\n\n1\n21\ntrain query 21\n\n\n\n\n\n\n\n\nsource\n\n\n\n\n PassageData.get_labels (type:str)\n\nGet labeled data\n\n\n\n\nType\nDetails\n\n\n\n\ntype\nstr\nEither ‘train’ or ‘dev’.\n\n\nReturns\ntyping.Dict\npyvespa-formatted labeled data\n\n\n\n\npassage_data.get_labels(type=\"train\")\n\n[{'query_id': '10',\n 'query': 'train query 10',\n 'relevant_docs': [{'id': '0', 'score': 1}]},\n {'query_id': '11',\n 'query': 'train query 11',\n 'relevant_docs': [{'id': '2', 'score': 1}]}]\n\n\n\npassage_data.get_labels(type=\"dev\")\n\n[{'query_id': '20',\n 'query': 'train query 20',\n 'relevant_docs': [{'id': '1', 'score': 1}]},\n {'query_id': '21',\n 'query': 'train query 21',\n 'relevant_docs': [{'id': '3', 'score': 1}]}]\n\n\n\nsource\n\n\n\n\n sample_data (n_relevant:int, n_irrelevant:int)\n\nSample data from the passage ranking dataset.\nThe final sample contains n_relevant train relevant documents, n_relevant dev relevant documents and n_irrelevant random documents sampled from the entire corpus.\nAll the relevant sampled documents, both from train and dev sets, are guaranteed to be on the corpus_sample, which will contain 2 * n_relevant + n_irrelevant documents.\n\n\n\n\nType\nDetails\n\n\n\n\nn_relevant\nint\nThe number of relevant documents to sample.\n\n\nn_irrelevant\nint\nThe number of non-judged documents to sample.\n\n\nReturns\nPassageData\n\n\n\n\nUsage:\n\nsample = sample_data(n_relevant=1, n_irrelevant=3)\n\nThe sampled corpus is a dict containing document id as key and the passage text as value.\n\nsample.corpus\n\n{'890370': 'the map of europe gives you a clear view of the political boundaries that segregate the countries in the continent including germany uk france spain italy greece romania ukraine hungary austria sweden finland norway czech republic belgium luxembourg switzerland croatia and albaniahe map of europe gives you a clear view of the political boundaries that segregate the countries in the continent including germany uk france spain italy greece romania ukraine hungary austria sweden finland norway czech republic belgium luxembourg switzerland croatia and albania',\n '5060205': 'Setting custom HTTP headers with cURL can be done by using the CURLOPT_HTTPHEADER option, which can be set with the curl_setopt function. To add headers to your HTTP request you need to put them into a PHP Array, which you can then pass to the cul_setopt function, like demonstrated in the below example.',\n '6096573': \"The sugar in RNA is ribose, whereas the sugar in DNA is deoxyribose. The only difference between the two is that in deoxyribose, there is an oxygen missing from the 2' carbon …(there is a H there instead of an OH). This makes DNA more stable/less reactive than RNA. 1 person found this useful.\",\n '3092885': 'All three C-Ph bonds are typical of sp 3 - sp 2 carbon-carbon bonds with lengths of approximately 1.47 A, å while The-C o bond length is approximately.1 42. A å the presence of three adjacent phenyl groups confers special properties manifested in the reactivity of. the alcohol',\n '7275560': 'shortest phase of mitosis Anaphase is the shortest phase of mitosis. During anaphase the arranged chromosomes at the metaphase plate are migrate towards their respective poles. Before this migration started, chromosomes are divided into sister chromatids, by the separation of joined centromere of two sister chromatids of a chromosomes.'}\n\n\nThe size of the sampled document corpus is equal to 2 * n_relevant + n_irrelevant.\n\nlen(sample.corpus)\n\n5\n\n\nSampled queries are dict containing query id as key and query text as value.\n\nprint(sample.train_queries)\nprint(sample.dev_queries)\n\n{'899723': 'what sugar is found in rna'}\n{'994205': 'which is the shortest stage in duration'}\n\n\nSampled qrels contains one relevant document for each query.\n\nprint(sample.train_qrels)\nprint(sample.dev_qrels)\n\n{'899723': {'6096573': 1}}\n{'994205': {'7275560': 1}}\n\n\nThe following relevant documents are guaranteed to be included in the corpus_sample.\n\n\n['6096573', '7275560']" + }, + { + "objectID": "module_passage.html#basic-search", + "href": "module_passage.html#basic-search", + "title": "passage", + "section": "Basic search", + "text": "Basic search\nCode related to a basic search search engine for passage ranking.\n\nsource\n\ncreate_basic_search_package\n\n create_basic_search_package (name:str='PassageRanking')\n\nCreate a basic Vespa application package for passage ranking.\nVespa fields:\nThe application contain two string fields: doc_id and text.\nVespa rank functions:\nThe application contain two rank profiles: bm25 and nativeRank.\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nname\nstr\nPassageRanking\nName of the application\n\n\nReturns\nApplicationPackage\n\npyvespa ApplicationPackage instance.\n\n\n\nUsage:\n\napp_package = create_basic_search_package(name=\"PassageModuleApp\")\n\nCheck how the Vespa schema definition for this application looks like:\n\nprint(app_package.schema.schema_to_text)\n\nschema PassageModuleApp {\n document PassageModuleApp {\n field doc_id type string {\n indexing: attribute | summary\n }\n field text type string {\n indexing: index | summary\n index: enable-bm25\n }\n }\n fieldset default {\n fields: text\n }\n rank-profile bm25 {\n first-phase {\n expression: bm25(text)\n }\n summary-features {\n bm25(text)\n }\n }\n rank-profile native_rank {\n first-phase {\n expression: nativeRank(text)\n }\n }\n}" + }, + { + "objectID": "module_passage.html#evaluate-query-models", + "href": "module_passage.html#evaluate-query-models", + "title": "passage", + "section": "Evaluate query models", + "text": "Evaluate query models\n\nsource\n\nevaluate_query_models\n\n evaluate_query_models (app_package:vespa.package.ApplicationPackage,\n query_models:List[learntorank.query.QueryModel],\n metrics:List[learntorank.evaluation.EvalMetric],\n corpus_size:List[int], output_file_path:str,\n dev_query_percentage:float=0.006285807802305023,\n verbose:bool=True, **kwargs)\n\n\nfrom learntorank.evaluation import (\n MatchRatio,\n Recall, \n ReciprocalRank, \n NormalizedDiscountedCumulativeGain\n)\nfrom learntorank.query import QueryModel, OR, Ranking\n\ncorpus_size = [100, 200]\napp_package = create_basic_search_package(name=\"PassageEvaluationApp\")\nquery_models = [\n QueryModel(\n name=\"bm25\", \n match_phase=OR(), \n ranking=Ranking(name=\"bm25\")\n ),\n QueryModel(\n name=\"native_rank\", \n match_phase=OR(), \n ranking=Ranking(name=\"native_rank\")\n )\n]\nmetrics = [\n MatchRatio(),\n Recall(at=100), \n ReciprocalRank(at=10), \n NormalizedDiscountedCumulativeGain(at=10)\n]\noutput_file_path = \"test.csv\"\n\n\nestimates = evaluate_query_models(\n app_package=app_package,\n query_models=query_models,\n metrics=metrics,\n corpus_size=corpus_size,\n dev_query_percentage=0.5,\n output_file_path=output_file_path, \n verbose=False\n)\n\n*****\nDeploy Vespa application:\n*****\nWaiting for configuration server, 0/300 seconds...\nWaiting for configuration server, 5/300 seconds...\nWaiting for configuration server, 10/300 seconds...\nWaiting for application status, 0/300 seconds...\nWaiting for application status, 5/300 seconds...\nWaiting for application status, 10/300 seconds...\nWaiting for application status, 15/300 seconds...\nWaiting for application status, 20/300 seconds...\nWaiting for application status, 25/300 seconds...\nWaiting for application status, 30/300 seconds...\nWaiting for application status, 35/300 seconds...\nWaiting for application status, 40/300 seconds...\nWaiting for application status, 45/300 seconds...\nWaiting for application status, 50/300 seconds...\nWaiting for application status, 55/300 seconds...\nWaiting for application status, 60/300 seconds...\nWaiting for application status, 65/300 seconds...\nWaiting for application status, 70/300 seconds...\nWaiting for application status, 75/300 seconds...\nWaiting for application status, 80/300 seconds..." + }, + { + "objectID": "module_stats.html", + "href": "module_stats.html", + "title": "stats", + "section": "", + "text": "source\n\n\n\n bootstrap_sampling (data:pandas.core.frame.DataFrame,\n estimator:Callable=<function mean>, n_boot:int=1000,\n columns_to_exclude:List[str]=None)\n\nCompute bootstrap estimates of the data distribution\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndata\nDataFrame\n\nData containing the columns we want to generate bootstrap estimates from.\n\n\nestimator\ntyping.Callable\nmean\nestimator function that accepts an array-like argument.\n\n\nn_boot\nint\n1000\nNumber of bootstrap estimates to compute.\n\n\ncolumns_to_exclude\ntyping.List[str]\nNone\nColumn names to exclude.\n\n\n\nUsage:\nGenerate data with columns containing data that we want to compute estimates from. The values in the column a comes from Normal distribution with mean 0 and standard deviation 1. The values from column b comes from Normal distribution with mean 100 and standard deviation 10.\n\ndata = pd.DataFrame(\n data={\n \"a\": np.random.normal(size = 100), \n \"b\": np.random.normal(loc=100, scale = 10, size = 100)\n }\n)\ndata.head()\n\n\n\n\n\n\n\n\na\nb\n\n\n\n\n0\n0.605639\n92.817505\n\n\n1\n-0.775791\n92.750026\n\n\n2\n-1.265231\n107.981771\n\n\n3\n0.981306\n101.388385\n\n\n4\n0.029075\n122.700172\n\n\n\n\n\n\n\n\n\nBy default, the function generates the mean of each column n_boot times. Each value represents the mean obtained from a bootstrap sample of the original data.\n\nestimates = bootstrap_sampling(data, n_boot=100)\nestimates\n\n\n\n\n\n\n\n\na\nb\n\n\n\n\n0\n0.012356\n100.018394\n\n\n1\n0.143189\n100.691872\n\n\n2\n-0.002554\n99.874399\n\n\n3\n0.079395\n99.539636\n\n\n4\n0.055096\n100.452383\n\n\n...\n...\n...\n\n\n95\n0.063409\n100.439363\n\n\n96\n-0.024455\n98.607045\n\n\n97\n0.209427\n99.866736\n\n\n98\n0.061323\n98.680469\n\n\n99\n0.289456\n99.980295\n\n\n\n\n100 rows × 2 columns\n\n\n\nWe can check if the estimates make sense by compute the mean of the bootstrap estimates and comparing with the mean of the Normal distribution they were generated from.\n\nestimates.mean()\n\na 0.089538\nb 100.099900\ndtype: float64\n\n\n\n\n\nWe can specify other functions, such as np.std to compute the standard deviation.\n\nestimates = bootstrap_sampling(data, estimator=np.std, n_boot=100)\nestimates\n\n\n\n\n\n\n\n\na\nb\n\n\n\n\n0\n0.933496\n10.126658\n\n\n1\n0.929125\n9.852667\n\n\n2\n0.899762\n10.307814\n\n\n3\n0.968039\n10.416074\n\n\n4\n1.004349\n10.441463\n\n\n...\n...\n...\n\n\n95\n0.910904\n10.357727\n\n\n96\n0.818276\n12.358640\n\n\n97\n0.981826\n9.622724\n\n\n98\n0.962237\n10.897055\n\n\n99\n0.913994\n11.096338\n\n\n\n\n100 rows × 2 columns\n\n\n\nIf we take the mean of the bootstrap estimates of the standard deviation, we should recover a value close to the standard deviation of the distribution that the data were generated from.\n\nestimates.mean()\n\na 0.943942\nb 10.480457\ndtype: float64\n\n\n\n\n\n\nestimates = bootstrap_sampling(\n data, n_boot=100, columns_to_exclude=[\"b\"]\n)\nestimates\n\n\n\n\n\n\n\n\na\n\n\n\n\n0\n0.259128\n\n\n1\n0.098232\n\n\n2\n0.087111\n\n\n3\n-0.131376\n\n\n4\n0.050997\n\n\n...\n...\n\n\n95\n0.129835\n\n\n96\n-0.004873\n\n\n97\n-0.046338\n\n\n98\n0.246239\n\n\n99\n0.355848\n\n\n\n\n100 rows × 1 columns\n\n\n\n\nsource\n\n\n\n\n\n compute_evaluation_estimates (df:pandas.core.frame.DataFrame,\n n_boot:int=1000,\n estimator:Callable=<function mean>,\n quantile_low:float=0.025,\n quantile_high=0.975)\n\nCompute estimate and confidence interval for evaluation per query metrics.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndf\nDataFrame\n\nEvaluations per query data, usually obtained pyvespa evaluate method.\n\n\nn_boot\nint\n1000\nNumber of bootstrap samples.\n\n\nestimator\ntyping.Callable\nmean\nestimator function that accepts an array-like argument.\n\n\nquantile_low\nfloat\n0.025\nlower quantile to compute confidence interval\n\n\nquantile_high\nfloat\n0.975\nupper quantile to compute confidence interval\n\n\n\nUsage:\nGenerate sample data frame, which must contain the column model.\n\nnumber_data_points = 1000\ndata = pd.DataFrame(\n data = {\n \"model\": (\n [\"A\"] * number_data_points + \n [\"B\"] * number_data_points\n ),\n \"query_id\": (\n list(range(number_data_points)) + \n list(range(number_data_points))\n ),\n \"metric_1\": (\n np.random.binomial(size=number_data_points, n=1, p=0.3).tolist() + \n np.random.binomial(size=number_data_points, n=1, p=0.7).tolist()\n ),\n \"metric_2\": (\n np.random.binomial(size=number_data_points, n=1, p=0.1).tolist() + \n np.random.binomial(size=number_data_points, n=1, p=0.9).tolist()\n )\n \n }\n).sort_values(\"query_id\").reset_index(drop=True)\ndata\n\n\n\n\n\n\n\n\nmodel\nquery_id\nmetric_1\nmetric_2\n\n\n\n\n0\nA\n0\n0\n0\n\n\n1\nB\n0\n1\n1\n\n\n2\nA\n1\n0\n1\n\n\n3\nB\n1\n1\n1\n\n\n4\nA\n2\n0\n0\n\n\n...\n...\n...\n...\n...\n\n\n1995\nA\n997\n1\n0\n\n\n1996\nB\n998\n1\n1\n\n\n1997\nA\n998\n1\n0\n\n\n1998\nA\n999\n0\n0\n\n\n1999\nB\n999\n0\n1\n\n\n\n\n2000 rows × 4 columns\n\n\n\n\n\n\ncompute_evaluation_estimates(data)\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.268000\n0.296\n0.325\n\n\n1\nmetric_1\nB\n0.667000\n0.696\n0.724\n\n\n2\nmetric_2\nA\n0.091000\n0.109\n0.129\n\n\n3\nmetric_2\nB\n0.887975\n0.907\n0.924\n\n\n\n\n\n\n\n\n\n\n\ncompute_evaluation_estimates(data, estimator=np.std)\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.442918\n0.456491\n0.468375\n\n\n1\nmetric_1\nB\n0.448001\n0.459983\n0.470931\n\n\n2\nmetric_2\nA\n0.289026\n0.311639\n0.335200\n\n\n3\nmetric_2\nB\n0.264998\n0.291829\n0.315366\n\n\n\n\n\n\n\n\n\n\n\ncompute_evaluation_estimates(\n data, \n quantile_low=0.2, \n quantile_high=0.8\n)\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.285\n0.296\n0.308\n\n\n1\nmetric_1\nB\n0.684\n0.696\n0.708\n\n\n2\nmetric_2\nA\n0.102\n0.110\n0.118\n\n\n3\nmetric_2\nB\n0.898\n0.906\n0.914\n\n\n\n\n\n\n\n\ncompute_evaluation_estimates(data[[\"model\", \"metric_1\", \"metric_2\"]])\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.269975\n0.297\n0.326000\n\n\n1\nmetric_1\nB\n0.667975\n0.696\n0.726000\n\n\n2\nmetric_2\nA\n0.091000\n0.109\n0.129025\n\n\n3\nmetric_2\nB\n0.888000\n0.907\n0.923000" + }, + { + "objectID": "module_stats.html#bootstrap-estimates", + "href": "module_stats.html#bootstrap-estimates", + "title": "stats", + "section": "", + "text": "source\n\n\n\n bootstrap_sampling (data:pandas.core.frame.DataFrame,\n estimator:Callable=<function mean>, n_boot:int=1000,\n columns_to_exclude:List[str]=None)\n\nCompute bootstrap estimates of the data distribution\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndata\nDataFrame\n\nData containing the columns we want to generate bootstrap estimates from.\n\n\nestimator\ntyping.Callable\nmean\nestimator function that accepts an array-like argument.\n\n\nn_boot\nint\n1000\nNumber of bootstrap estimates to compute.\n\n\ncolumns_to_exclude\ntyping.List[str]\nNone\nColumn names to exclude.\n\n\n\nUsage:\nGenerate data with columns containing data that we want to compute estimates from. The values in the column a comes from Normal distribution with mean 0 and standard deviation 1. The values from column b comes from Normal distribution with mean 100 and standard deviation 10.\n\ndata = pd.DataFrame(\n data={\n \"a\": np.random.normal(size = 100), \n \"b\": np.random.normal(loc=100, scale = 10, size = 100)\n }\n)\ndata.head()\n\n\n\n\n\n\n\n\na\nb\n\n\n\n\n0\n0.605639\n92.817505\n\n\n1\n-0.775791\n92.750026\n\n\n2\n-1.265231\n107.981771\n\n\n3\n0.981306\n101.388385\n\n\n4\n0.029075\n122.700172\n\n\n\n\n\n\n\n\n\nBy default, the function generates the mean of each column n_boot times. Each value represents the mean obtained from a bootstrap sample of the original data.\n\nestimates = bootstrap_sampling(data, n_boot=100)\nestimates\n\n\n\n\n\n\n\n\na\nb\n\n\n\n\n0\n0.012356\n100.018394\n\n\n1\n0.143189\n100.691872\n\n\n2\n-0.002554\n99.874399\n\n\n3\n0.079395\n99.539636\n\n\n4\n0.055096\n100.452383\n\n\n...\n...\n...\n\n\n95\n0.063409\n100.439363\n\n\n96\n-0.024455\n98.607045\n\n\n97\n0.209427\n99.866736\n\n\n98\n0.061323\n98.680469\n\n\n99\n0.289456\n99.980295\n\n\n\n\n100 rows × 2 columns\n\n\n\nWe can check if the estimates make sense by compute the mean of the bootstrap estimates and comparing with the mean of the Normal distribution they were generated from.\n\nestimates.mean()\n\na 0.089538\nb 100.099900\ndtype: float64\n\n\n\n\n\nWe can specify other functions, such as np.std to compute the standard deviation.\n\nestimates = bootstrap_sampling(data, estimator=np.std, n_boot=100)\nestimates\n\n\n\n\n\n\n\n\na\nb\n\n\n\n\n0\n0.933496\n10.126658\n\n\n1\n0.929125\n9.852667\n\n\n2\n0.899762\n10.307814\n\n\n3\n0.968039\n10.416074\n\n\n4\n1.004349\n10.441463\n\n\n...\n...\n...\n\n\n95\n0.910904\n10.357727\n\n\n96\n0.818276\n12.358640\n\n\n97\n0.981826\n9.622724\n\n\n98\n0.962237\n10.897055\n\n\n99\n0.913994\n11.096338\n\n\n\n\n100 rows × 2 columns\n\n\n\nIf we take the mean of the bootstrap estimates of the standard deviation, we should recover a value close to the standard deviation of the distribution that the data were generated from.\n\nestimates.mean()\n\na 0.943942\nb 10.480457\ndtype: float64\n\n\n\n\n\n\nestimates = bootstrap_sampling(\n data, n_boot=100, columns_to_exclude=[\"b\"]\n)\nestimates\n\n\n\n\n\n\n\n\na\n\n\n\n\n0\n0.259128\n\n\n1\n0.098232\n\n\n2\n0.087111\n\n\n3\n-0.131376\n\n\n4\n0.050997\n\n\n...\n...\n\n\n95\n0.129835\n\n\n96\n-0.004873\n\n\n97\n-0.046338\n\n\n98\n0.246239\n\n\n99\n0.355848\n\n\n\n\n100 rows × 1 columns\n\n\n\n\nsource\n\n\n\n\n\n compute_evaluation_estimates (df:pandas.core.frame.DataFrame,\n n_boot:int=1000,\n estimator:Callable=<function mean>,\n quantile_low:float=0.025,\n quantile_high=0.975)\n\nCompute estimate and confidence interval for evaluation per query metrics.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndf\nDataFrame\n\nEvaluations per query data, usually obtained pyvespa evaluate method.\n\n\nn_boot\nint\n1000\nNumber of bootstrap samples.\n\n\nestimator\ntyping.Callable\nmean\nestimator function that accepts an array-like argument.\n\n\nquantile_low\nfloat\n0.025\nlower quantile to compute confidence interval\n\n\nquantile_high\nfloat\n0.975\nupper quantile to compute confidence interval\n\n\n\nUsage:\nGenerate sample data frame, which must contain the column model.\n\nnumber_data_points = 1000\ndata = pd.DataFrame(\n data = {\n \"model\": (\n [\"A\"] * number_data_points + \n [\"B\"] * number_data_points\n ),\n \"query_id\": (\n list(range(number_data_points)) + \n list(range(number_data_points))\n ),\n \"metric_1\": (\n np.random.binomial(size=number_data_points, n=1, p=0.3).tolist() + \n np.random.binomial(size=number_data_points, n=1, p=0.7).tolist()\n ),\n \"metric_2\": (\n np.random.binomial(size=number_data_points, n=1, p=0.1).tolist() + \n np.random.binomial(size=number_data_points, n=1, p=0.9).tolist()\n )\n \n }\n).sort_values(\"query_id\").reset_index(drop=True)\ndata\n\n\n\n\n\n\n\n\nmodel\nquery_id\nmetric_1\nmetric_2\n\n\n\n\n0\nA\n0\n0\n0\n\n\n1\nB\n0\n1\n1\n\n\n2\nA\n1\n0\n1\n\n\n3\nB\n1\n1\n1\n\n\n4\nA\n2\n0\n0\n\n\n...\n...\n...\n...\n...\n\n\n1995\nA\n997\n1\n0\n\n\n1996\nB\n998\n1\n1\n\n\n1997\nA\n998\n1\n0\n\n\n1998\nA\n999\n0\n0\n\n\n1999\nB\n999\n0\n1\n\n\n\n\n2000 rows × 4 columns\n\n\n\n\n\n\ncompute_evaluation_estimates(data)\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.268000\n0.296\n0.325\n\n\n1\nmetric_1\nB\n0.667000\n0.696\n0.724\n\n\n2\nmetric_2\nA\n0.091000\n0.109\n0.129\n\n\n3\nmetric_2\nB\n0.887975\n0.907\n0.924\n\n\n\n\n\n\n\n\n\n\n\ncompute_evaluation_estimates(data, estimator=np.std)\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.442918\n0.456491\n0.468375\n\n\n1\nmetric_1\nB\n0.448001\n0.459983\n0.470931\n\n\n2\nmetric_2\nA\n0.289026\n0.311639\n0.335200\n\n\n3\nmetric_2\nB\n0.264998\n0.291829\n0.315366\n\n\n\n\n\n\n\n\n\n\n\ncompute_evaluation_estimates(\n data, \n quantile_low=0.2, \n quantile_high=0.8\n)\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.285\n0.296\n0.308\n\n\n1\nmetric_1\nB\n0.684\n0.696\n0.708\n\n\n2\nmetric_2\nA\n0.102\n0.110\n0.118\n\n\n3\nmetric_2\nB\n0.898\n0.906\n0.914\n\n\n\n\n\n\n\n\ncompute_evaluation_estimates(data[[\"model\", \"metric_1\", \"metric_2\"]])\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nmetric_1\nA\n0.269975\n0.297\n0.326000\n\n\n1\nmetric_1\nB\n0.667975\n0.696\n0.726000\n\n\n2\nmetric_2\nA\n0.091000\n0.109\n0.129025\n\n\n3\nmetric_2\nB\n0.888000\n0.907\n0.923000" + }, + { + "objectID": "module_query.html", + "href": "module_query.html", + "title": "query", + "section": "", + "text": "source\n\n\n\n MatchFilter ()\n\nAbstract class for match filters.\n\nsource\n\n\n\n\n AND ()\n\nFilter that match document containing all the query terms.\nUsage: The AND filter is usually used when specifying query models.\n\nand_filter = AND()\n\n\nsource\n\n\n\n\n OR ()\n\nFilter that match any document containing at least one query term.\nUsage: The OR filter is usually used when specifying query models.\n\nor_filter = OR()\n\n\nsource\n\n\n\n\n WeakAnd (hits:int, field:str='default')\n\nMatch documents according to the weakAND algorithm.\nReference: https://docs.vespa.ai/en/using-wand-with-vespa.html\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nhits\nint\n\nLower bound on the number of hits to be retrieved.\n\n\nfield\nstr\ndefault\nWhich Vespa field to search.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: The WeakAnd filter is usually used when specifying query models.\n\nweakand_filter = WeakAnd(hits=10, field=\"default\")\n\n\nsource\n\n\n\n\n Tokenize (hits:int, field:str='default')\n\nMatch documents according to the weakAND algorithm without parsing specials characters.\nReference: https://docs.vespa.ai/en/reference/simple-query-language-reference.html\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nhits\nint\n\nLower bound on the number of hits to be retrieved.\n\n\nfield\nstr\ndefault\nWhich Vespa field to search.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: The Tokenize filter is usually used when specifying query models.\n\ntokenize_filter = Tokenize(hits=10, field=\"default\")\n\n\nsource\n\n\n\n\n ANN (doc_vector:str, query_vector:str, hits:int, label:str,\n approximate:bool=True)\n\nMatch documents according to the nearest neighbor operator.\nReference: https://docs.vespa.ai/en/reference/query-language-reference.html\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndoc_vector\nstr\n\nName of the document field to be used in the distance calculation.\n\n\nquery_vector\nstr\n\nName of the query field to be used in the distance calculation.\n\n\nhits\nint\n\nLower bound on the number of hits to return.\n\n\nlabel\nstr\n\nA label to identify this specific operator instance.\n\n\napproximate\nbool\nTrue\nTrue to use approximate nearest neighbor and False to use brute force. Default to True.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: The ANN filter is usually used when specifying query models.\nBy default, the ANN operator uses approximate nearest neighbor:\n\nmatch_filter = ANN(\n doc_vector=\"doc_vector\",\n query_vector=\"query_vector\",\n hits=10,\n label=\"label\",\n)\n\nBrute-force can be used by specifying approximate=False:\n\nann_filter = ANN(\n doc_vector=\"doc_vector\",\n query_vector=\"query_vector\",\n hits=10,\n label=\"label\",\n approximate=False,\n)\n\n\nsource\n\n\n\n\n Union (*args:__main__.MatchFilter)\n\nMatch documents that belongs to the union of many match filters.\n\n\n\n\nType\nDetails\n\n\n\n\nargs\nMatchFilter\n\n\n\nReturns\nNone\nMatch filters to be taken the union of.\n\n\n\nUsage: The Union filter is usually used when specifying query models.\n\nunion_filter = Union(\n WeakAnd(hits=10, field=\"field_name\"),\n ANN(\n doc_vector=\"doc_vector\",\n query_vector=\"query_vector\",\n hits=10,\n label=\"label\",\n ),\n)" + }, + { + "objectID": "module_query.html#match-filters", + "href": "module_query.html#match-filters", + "title": "query", + "section": "", + "text": "source\n\n\n\n MatchFilter ()\n\nAbstract class for match filters.\n\nsource\n\n\n\n\n AND ()\n\nFilter that match document containing all the query terms.\nUsage: The AND filter is usually used when specifying query models.\n\nand_filter = AND()\n\n\nsource\n\n\n\n\n OR ()\n\nFilter that match any document containing at least one query term.\nUsage: The OR filter is usually used when specifying query models.\n\nor_filter = OR()\n\n\nsource\n\n\n\n\n WeakAnd (hits:int, field:str='default')\n\nMatch documents according to the weakAND algorithm.\nReference: https://docs.vespa.ai/en/using-wand-with-vespa.html\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nhits\nint\n\nLower bound on the number of hits to be retrieved.\n\n\nfield\nstr\ndefault\nWhich Vespa field to search.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: The WeakAnd filter is usually used when specifying query models.\n\nweakand_filter = WeakAnd(hits=10, field=\"default\")\n\n\nsource\n\n\n\n\n Tokenize (hits:int, field:str='default')\n\nMatch documents according to the weakAND algorithm without parsing specials characters.\nReference: https://docs.vespa.ai/en/reference/simple-query-language-reference.html\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nhits\nint\n\nLower bound on the number of hits to be retrieved.\n\n\nfield\nstr\ndefault\nWhich Vespa field to search.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: The Tokenize filter is usually used when specifying query models.\n\ntokenize_filter = Tokenize(hits=10, field=\"default\")\n\n\nsource\n\n\n\n\n ANN (doc_vector:str, query_vector:str, hits:int, label:str,\n approximate:bool=True)\n\nMatch documents according to the nearest neighbor operator.\nReference: https://docs.vespa.ai/en/reference/query-language-reference.html\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndoc_vector\nstr\n\nName of the document field to be used in the distance calculation.\n\n\nquery_vector\nstr\n\nName of the query field to be used in the distance calculation.\n\n\nhits\nint\n\nLower bound on the number of hits to return.\n\n\nlabel\nstr\n\nA label to identify this specific operator instance.\n\n\napproximate\nbool\nTrue\nTrue to use approximate nearest neighbor and False to use brute force. Default to True.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: The ANN filter is usually used when specifying query models.\nBy default, the ANN operator uses approximate nearest neighbor:\n\nmatch_filter = ANN(\n doc_vector=\"doc_vector\",\n query_vector=\"query_vector\",\n hits=10,\n label=\"label\",\n)\n\nBrute-force can be used by specifying approximate=False:\n\nann_filter = ANN(\n doc_vector=\"doc_vector\",\n query_vector=\"query_vector\",\n hits=10,\n label=\"label\",\n approximate=False,\n)\n\n\nsource\n\n\n\n\n Union (*args:__main__.MatchFilter)\n\nMatch documents that belongs to the union of many match filters.\n\n\n\n\nType\nDetails\n\n\n\n\nargs\nMatchFilter\n\n\n\nReturns\nNone\nMatch filters to be taken the union of.\n\n\n\nUsage: The Union filter is usually used when specifying query models.\n\nunion_filter = Union(\n WeakAnd(hits=10, field=\"field_name\"),\n ANN(\n doc_vector=\"doc_vector\",\n query_vector=\"query_vector\",\n hits=10,\n label=\"label\",\n ),\n)" + }, + { + "objectID": "module_query.html#ranking", + "href": "module_query.html#ranking", + "title": "query", + "section": "Ranking", + "text": "Ranking\n\nsource\n\nRanking\n\n Ranking (name:str='default', list_features:bool=False)\n\nDefine the rank profile to be used during ranking.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nname\nstr\ndefault\nName of the rank profile as defined in a Vespa search definition.\n\n\nlist_features\nbool\nFalse\nShould the ranking features be returned. Either ‘true’ or ‘false’.\n\n\nReturns\nNone\n\n\n\n\n\nUsage: Ranking is usually used when specifying query models.\n\nranking = Ranking(name=\"bm25\", list_features=True)" + }, + { + "objectID": "module_query.html#query-properties", + "href": "module_query.html#query-properties", + "title": "query", + "section": "Query properties", + "text": "Query properties\n\nsource\n\nQueryProperty\n\n QueryProperty ()\n\nAbstract class for query property.\n\nsource\n\n\nQueryRankingFeature\n\n QueryRankingFeature (name:str, mapping:Callable[[str],List[float]])\n\nInclude ranking.feature.query into a Vespa query.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nname\nstr\nName of the feature.\n\n\nmapping\ntyping.Callable[[str], typing.List[float]]\nFunction mapping a string to a list of floats.\n\n\nReturns\nNone\n\n\n\n\nUsage: QueryRankingFeature is usually used when specifying query models.\n\nquery_property = QueryRankingFeature(\n name=\"query_vector\", mapping=lambda x: [1, 2, 3]\n)" + }, + { + "objectID": "module_query.html#query-model", + "href": "module_query.html#query-model", + "title": "query", + "section": "Query model", + "text": "Query model\n\nsource\n\nQueryModel\n\n QueryModel (name:str='default_name',\n query_properties:Optional[List[__main__.QueryProperty]]=None,\n match_phase:__main__.MatchFilter=<__main__.AND object at\n 0x7f8fe5e0da30>, ranking:__main__.Ranking=<__main__.Ranking\n object at 0x7f8fe5e0da90>,\n body_function:Optional[Callable[[str],Dict]]=None)\n\nDefine a query model.\nA QueryModel is an abstraction that encapsulates all the relevant information controlling how a Vespa app matches and ranks documents.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nname\nstr\ndefault_name\nName of the query model. Used to tag model-related quantities, like evaluation metrics.\n\n\nquery_properties\ntyping.Optional[typing.List[main.QueryProperty]]\nNone\nQuery properties to be included in the queries.\n\n\nmatch_phase\nMatchFilter\n<main.AND object at 0x7f8fe5e0da30>\nDefine the match criteria.\n\n\nranking\nRanking\n<main.Ranking object at 0x7f8fe5e0da90>\nDefine the rank criteria.\n\n\nbody_function\ntyping.Optional[typing.Callable[[str], typing.Dict]]\nNone\nFunction that take query as parameter and returns the body of a Vespa query.\n\n\nReturns\nNone\n\n\n\n\n\nUsage:\nSpecify a query model with default configurations:\n\nquery_model = QueryModel()\n\nSpecify match phase, ranking phase and properties used by them.\n\nquery_model = QueryModel(\n query_properties=[\n QueryRankingFeature(name=\"query_embedding\", mapping=lambda x: [1, 2, 3])\n ],\n match_phase=ANN(\n doc_vector=\"document_embedding\",\n query_vector=\"query_embedding\",\n hits=10,\n label=\"label\",\n ),\n ranking=Ranking(name=\"bm25_plus_embeddings\", list_features=True),\n)\n\nSpecify a query model based on a function that output Vespa YQL.\n\ndef body_function(query):\n body = {\n \"yql\": \"select * from sources * where userQuery();\",\n \"query\": query,\n \"type\": \"any\",\n \"ranking\": {\"profile\": \"bm25\", \"listFeatures\": \"true\"},\n }\n return body\n\nquery_model = QueryModel(body_function=body_function)" + }, + { + "objectID": "module_query.html#send-query-with-querymodel", + "href": "module_query.html#send-query-with-querymodel", + "title": "query", + "section": "Send query with QueryModel", + "text": "Send query with QueryModel\n\nsource\n\nsend_query\n\n send_query (app:vespa.application.Vespa, body:Optional[Dict]=None,\n query:Optional[str]=None,\n query_model:Optional[__main__.QueryModel]=None,\n debug_request:bool=False, recall:Optional[Tuple]=None,\n **kwargs)\n\nSend a query request to a Vespa application.\nEither send ‘body’ containing all the request parameters or specify ‘query’ and ‘query_model’.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp\nVespa\n\nConnection to a Vespa application\n\n\nbody\ntyping.Optional[typing.Dict]\nNone\nContains all the request parameters. None when using query_model.\n\n\nquery\ntyping.Optional[str]\nNone\nQuery string. None when using body.\n\n\nquery_model\ntyping.Optional[main.QueryModel]\nNone\nQuery model. None when using body.\n\n\ndebug_request\nbool\nFalse\nReturn request body for debugging instead of sending the request.\n\n\nrecall\ntyping.Optional[typing.Tuple]\nNone\nTuple of size 2 where the first element is the name of the field to use to recall and the second element is a list of the values to be recalled.\n\n\nkwargs\n\n\n\n\n\nReturns\nVespaQueryResponse\n\nEither the request body if debug_request is True or the result from the Vespa application.\n\n\n\nUsage: Assume app is a Vespa connection.\nSend request body.\n\nbody = {\"yql\": \"select * from sources * where test\"}\nresult = send_query(app=app, body=body)\n\nUse query and query_model:\n\nresult = send_query(\n app=app,\n query=\"this is a test\",\n query_model=QueryModel(\n match_phase=OR(), \n ranking=Ranking()\n ),\n hits=10,\n)\n\nDebug the output of the QueryModel by setting debug_request=True:\n\nsend_query(\n app=app,\n query=\"this is a test\",\n query_model=QueryModel(match_phase=OR(), ranking=Ranking()),\n debug_request=True,\n hits=10,\n).request_body\n\n{'yql': 'select * from sources * where ({grammar: \"any\"}userInput(\"this is a test\"));',\n 'ranking': {'profile': 'default', 'listFeatures': 'false'},\n 'hits': 10}\n\n\nRecall documents using the id field:\n\nresult = send_query(\n app=app,\n query=\"this is a test\",\n query_model=QueryModel(match_phase=OR(), ranking=Ranking()),\n hits=10,\n recall=(\"id\", [1, 5]),\n)\n\nUse a body_function to specify a QueryModel:\n\ndef body_function(query):\n body = {\n \"yql\": \"select * from sources * where userQuery();\",\n \"query\": query,\n \"type\": \"any\",\n \"ranking\": {\"profile\": \"bm25\", \"listFeatures\": \"true\"},\n }\n return body\n\nquery_model = QueryModel(body_function=body_function)\n\nresult = send_query(\n app=app,\n query=\"this is a test\",\n query_model=query_model,\n hits=10\n)\n\n\nsource\n\n\nsend_query_batch\n\n send_query_batch (app, body_batch:Optional[List[Dict]]=None,\n query_batch:Optional[List[str]]=None,\n query_model:Optional[__main__.QueryModel]=None,\n recall_batch:Optional[List[Tuple]]=None,\n asynchronous=True, connections:Optional[int]=100,\n total_timeout:int=100, **kwargs)\n\nSend queries in batch to a Vespa app.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp\n\n\nConnection to a Vespa application\n\n\nbody_batch\ntyping.Optional[typing.List[typing.Dict]]\nNone\nContains all the request parameters. Set to None if using ‘query_batch’.\n\n\nquery_batch\ntyping.Optional[typing.List[str]]\nNone\nQuery strings. Set to None if using ‘body_batch’.\n\n\nquery_model\ntyping.Optional[main.QueryModel]\nNone\nQuery model to use when sending query strings. Set to None if using ‘body_batch’.\n\n\nrecall_batch\ntyping.Optional[typing.List[typing.Tuple]]\nNone\nOne tuple for each query. Tuple of size 2 where the first element is the name of the field to use to recall and the second element is a list of the values to be recalled.\n\n\nasynchronous\nbool\nTrue\nSet True to send data in async mode. Default to True.\n\n\nconnections\ntyping.Optional[int]\n100\nNumber of allowed concurrent connections, valid only if asynchronous=True.\n\n\ntotal_timeout\nint\n100\nTotal timeout in secs for each of the concurrent requests when using asynchronous=True.\n\n\nkwargs\n\n\n\n\n\nReturns\ntyping.List[vespa.io.VespaQueryResponse]\n\nHTTP POST responses.\n\n\n\nUse body_batch to send a batch of body requests.\n\nbody_batch = [\n {\"yql\": \"select * from sources * where test\"},\n {\"yql\": \"select * from sources * where test2\"}\n]\nresult = send_query_batch(app=app, body_batch=body_batch)\n\nUse query_batch to send a batch of query strings to be ranked according a QueryModel.\n\nresult = send_query_batch(\n app=app,\n query_batch=[\"this is a test\", \"this is a test 2\"],\n query_model=QueryModel(\n match_phase=OR(), \n ranking=Ranking()\n ),\n hits=10,\n)\n\nUse recall_batch to send one tuple for each query in query_batch.\n\nresult = send_query_batch(\n app=app,\n query_batch=[\"this is a test\", \"this is a test 2\"],\n query_model=QueryModel(match_phase=OR(), ranking=Ranking()),\n hits=10,\n recall_batch=[(\"doc_id\", [2, 7]), (\"doc_id\", [0, 5])],\n)" + }, + { + "objectID": "module_query.html#collect-vespa-features", + "href": "module_query.html#collect-vespa-features", + "title": "query", + "section": "Collect Vespa features", + "text": "Collect Vespa features\n\nsource\n\ncollect_vespa_features\n\n collect_vespa_features (app:vespa.application.Vespa, labeled_data,\n id_field:str, query_model:__main__.QueryModel,\n number_additional_docs:int, fields:List[str],\n keep_features:Optional[List[str]]=None,\n relevant_score:int=1, default_score:int=0,\n **kwargs)\n\nCollect Vespa features based on a set of labelled data.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp\nVespa\n\nConnection to a Vespa application.\n\n\nlabeled_data\n\n\nLabelled data containing query, query_id and relevant ids. See examples about data format.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\nquery_model\nQueryModel\n\nQuery model.\n\n\nnumber_additional_docs\nint\n\nNumber of additional documents to retrieve for each relevant document. Duplicate documents will be dropped.\n\n\nfields\ntyping.List[str]\n\nVespa fields to collect, e.g. [“rankfeatures”, “summaryfeatures”]\n\n\nkeep_features\ntyping.Optional[typing.List[str]]\nNone\nList containing the names of the features that should be returned. Default to None, which return all the features contained in the ‘fields’ argument.\n\n\nrelevant_score\nint\n1\nScore to assign to relevant documents. Default to 1.\n\n\ndefault_score\nint\n0\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\nkwargs\n\n\n\n\n\nReturns\nDataFrame\n\nDataFrame containing document id (document_id), query id (query_id), scores (relevant) and vespa rank features returned by the Query model RankProfile used.\n\n\n\nUsage:\nDefine labeled_data as a list of dict containing relevant documents:\n\nlabeled_data = [\n {\n \"query_id\": 0,\n \"query\": \"give me title 1\",\n \"relevant_docs\": [{\"id\": \"1\", \"score\": 1}],\n },\n {\n \"query_id\": 1,\n \"query\": \"give me title 3\",\n \"relevant_docs\": [{\"id\": \"3\", \"score\": 1}],\n },\n]\n\nCollect vespa features:\n\nrank_features = collect_vespa_features(\n app=app,\n labeled_data=labeled_data,\n id_field=\"doc_id\",\n query_model=QueryModel(\n match_phase=OR(), \n ranking=Ranking(name=\"bm25\", list_features=True)\n ),\n number_additional_docs=2,\n fields=[\"rankfeatures\"],\n)\nrank_features\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nattributeMatch(doc_id)\nattributeMatch(doc_id).averageWeight\nattributeMatch(doc_id).completeness\nattributeMatch(doc_id).fieldCompleteness\nattributeMatch(doc_id).importance\nattributeMatch(doc_id).matches\nattributeMatch(doc_id).maxWeight\n...\nterm(3).significance\nterm(3).weight\nterm(4).connectedness\nterm(4).significance\nterm(4).weight\ntextSimilarity(text).fieldCoverage\ntextSimilarity(text).order\ntextSimilarity(text).proximity\ntextSimilarity(text).queryCoverage\ntextSimilarity(text).score\n\n\n\n\n0\n1\n0\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.50\n1.0\n1.000000\n0.50\n0.750000\n\n\n3\n7\n0\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.25\n0.0\n0.859375\n0.25\n0.425781\n\n\n1\n3\n1\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.50\n1.0\n1.000000\n0.50\n0.750000\n\n\n5\n7\n1\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.25\n0.0\n0.859375\n0.25\n0.425781\n\n\n\n\n4 rows × 94 columns\n\n\n\nUse a DataFrame for labeled_data instead of a list of dict:\n\nlabeled_data = [\n {\n \"qid\": 0,\n \"query\": \"give me title 1\",\n \"doc_id\": 1, \n \"relevance\": 1\n },\n {\n \"qid\": 1,\n \"query\": \"give me title 3\",\n \"doc_id\": 3, \n \"relevance\": 1\n },\n]\nlabeled_data_df = DataFrame.from_records(labeled_data)\nlabeled_data_df\n\n\n\n\n\n\n\n\nqid\nquery\ndoc_id\nrelevance\n\n\n\n\n0\n0\ngive me title 1\n1\n1\n\n\n1\n1\ngive me title 3\n3\n1\n\n\n\n\n\n\n\n\nrank_features = collect_vespa_features(\n app=app,\n labeled_data=labeled_data_df,\n id_field=\"doc_id\",\n query_model=QueryModel(\n match_phase=OR(), ranking=Ranking(name=\"bm25\", list_features=True)\n ),\n number_additional_docs=2,\n fields=[\"rankfeatures\"],\n)\nrank_features\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nattributeMatch(doc_id)\nattributeMatch(doc_id).averageWeight\nattributeMatch(doc_id).completeness\nattributeMatch(doc_id).fieldCompleteness\nattributeMatch(doc_id).importance\nattributeMatch(doc_id).matches\nattributeMatch(doc_id).maxWeight\n...\nterm(3).significance\nterm(3).weight\nterm(4).connectedness\nterm(4).significance\nterm(4).weight\ntextSimilarity(text).fieldCoverage\ntextSimilarity(text).order\ntextSimilarity(text).proximity\ntextSimilarity(text).queryCoverage\ntextSimilarity(text).score\n\n\n\n\n0\n1\n0\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.50\n1.0\n1.000000\n0.50\n0.750000\n\n\n3\n7\n0\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.25\n0.0\n0.859375\n0.25\n0.425781\n\n\n1\n3\n1\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.50\n1.0\n1.000000\n0.50\n0.750000\n\n\n5\n7\n1\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.583333\n100.0\n0.0\n0.0\n0.0\n0.25\n0.0\n0.859375\n0.25\n0.425781\n\n\n\n\n4 rows × 94 columns\n\n\n\nKeep only selected features by specifying their names in the keep_features argument:\n\nrank_features = collect_vespa_features(\n app=app,\n labeled_data=labeled_data_df,\n id_field=\"doc_id\",\n query_model=QueryModel(\n match_phase=OR(), ranking=Ranking(name=\"bm25\", list_features=True)\n ),\n number_additional_docs=2,\n fields=[\"rankfeatures\"],\n keep_features=[\"textSimilarity(text).score\"],\n)\nrank_features\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\ntextSimilarity(text).score\n\n\n\n\n0\n1\n0\n0\n0.750000\n\n\n3\n7\n0\n0\n0.425781\n\n\n1\n3\n1\n0\n0.750000\n\n\n5\n7\n1\n0\n0.425781\n\n\n\n\n\n\n\n\nsource\n\n\nstore_vespa_features\n\n store_vespa_features (app:vespa.application.Vespa, output_file_path:str,\n labeled_data, id_field:str,\n query_model:__main__.QueryModel,\n number_additional_docs:int, fields:List[str],\n keep_features:Optional[List[str]]=None,\n relevant_score:int=1, default_score:int=0,\n batch_size=1000, **kwargs)\n\nRetrieve Vespa rank features and store them in a .csv file.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp\nVespa\n\nConnection to a Vespa application.\n\n\noutput_file_path\nstr\n\nPath of the .csv output file. It will create the file of it does not exist and append the vespa features to an pre-existing file.\n\n\nlabeled_data\n\n\nLabelled data containing query, query_id and relevant ids. See details about data format.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\nquery_model\nQueryModel\n\nQuery model.\n\n\nnumber_additional_docs\nint\n\nNumber of additional documents to retrieve for each relevant document.\n\n\nfields\ntyping.List[str]\n\nList of Vespa fields to collect, e.g. [“rankfeatures”, “summaryfeatures”]\n\n\nkeep_features\ntyping.Optional[typing.List[str]]\nNone\nList containing the names of the features that should be returned. Default to None, which return all the features contained in the ‘fields’ argument.\n\n\nrelevant_score\nint\n1\nScore to assign to relevant documents.\n\n\ndefault_score\nint\n0\nScore to assign to the additional documents that are not relevant.\n\n\nbatch_size\nint\n1000\nThe size of the batch of labeled data points to be processed.\n\n\nkwargs\n\n\n\n\n\nReturns\nint\n\nreturns 0 upon success.\n\n\n\nUsage:\n\nlabeled_data = [\n {\n \"query_id\": 0,\n \"query\": \"give me title 1\",\n \"relevant_docs\": [{\"id\": \"1\", \"score\": 1}],\n },\n {\n \"query_id\": 1,\n \"query\": \"give me title 3\",\n \"relevant_docs\": [{\"id\": \"3\", \"score\": 1}],\n },\n]\n\nstore_vespa_features(\n app=app,\n output_file_path=\"vespa_features.csv\",\n labeled_data=labeled_data,\n id_field=\"doc_id\",\n query_model=QueryModel(\n match_phase=OR(), ranking=Ranking(name=\"bm25\", list_features=True)\n ),\n number_additional_docs=2,\n fields=[\"rankfeatures\", \"summaryfeatures\"],\n)\nrank_features = read_csv(\"vespa_features.csv\")\nrank_features\n\nRows collected: 4.\nBatch progress: 1/1.\n\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nattributeMatch(doc_id)\nattributeMatch(doc_id).averageWeight\nattributeMatch(doc_id).completeness\nattributeMatch(doc_id).fieldCompleteness\nattributeMatch(doc_id).importance\nattributeMatch(doc_id).matches\nattributeMatch(doc_id).maxWeight\n...\nterm(3).weight\nterm(4).connectedness\nterm(4).significance\nterm(4).weight\ntextSimilarity(text).fieldCoverage\ntextSimilarity(text).order\ntextSimilarity(text).proximity\ntextSimilarity(text).queryCoverage\ntextSimilarity(text).score\nvespa.summaryFeatures.cached\n\n\n\n\n0\n1\n0\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n100.0\n0.0\n0.0\n0.0\n0.50\n1.0\n1.000000\n0.50\n0.750000\n0.0\n\n\n1\n7\n0\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n100.0\n0.0\n0.0\n0.0\n0.25\n0.0\n0.859375\n0.25\n0.425781\n0.0\n\n\n2\n3\n1\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n100.0\n0.0\n0.0\n0.0\n0.50\n1.0\n1.000000\n0.50\n0.750000\n0.0\n\n\n3\n7\n1\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n100.0\n0.0\n0.0\n0.0\n0.25\n0.0\n0.859375\n0.25\n0.425781\n0.0\n\n\n\n\n4 rows × 95 columns" + }, + { + "objectID": "passage_uncertainty_evaluation.html", + "href": "passage_uncertainty_evaluation.html", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "", + "text": "When working with search engine apps, be it a text search or a recommendation system, part of the job is doing experiments around components such as ranking functions and deciding which experiments deliver the best result.\nThis tutorial builds a text search app with Vespa, feeds a sample of the passage ranking dataset to the app, and evaluates two ranking functions across three different metrics. In addition to return point estimates of the evaluation metrics, we compute confidence intervals as illustrated in the plot below. Measuring uncertainty around the metric estimates gives us a better sense of how significant is the impact of our changes in the application.\nThe code and the data used in this end-to-end tutorial are available and can be reproduced in a Jupyter Notebook." + }, + { + "objectID": "passage_uncertainty_evaluation.html#create-the-vespa-application-package", + "href": "passage_uncertainty_evaluation.html#create-the-vespa-application-package", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Create the Vespa application package", + "text": "Create the Vespa application package\nCreate a Vespa application package to perform passage ranking experiments using the create_basic_search_package.\n\nfrom learntorank.passage import create_basic_search_package\n\napp_package = create_basic_search_package()\n\nWe can inspect how the Vespa search definition file looks like:\n\nprint(app_package.schema.schema_to_text)\n\nschema PassageRanking {\n document PassageRanking {\n field doc_id type string {\n indexing: attribute | summary\n }\n field text type string {\n indexing: index | summary\n index: enable-bm25\n }\n }\n fieldset default {\n fields: text\n }\n rank-profile bm25 {\n first-phase {\n expression: bm25(text)\n }\n summary-features {\n bm25(text)\n }\n }\n rank-profile native_rank {\n first-phase {\n expression: nativeRank(text)\n }\n }\n}\n\n\nIn this tutorial, we are going to compare two ranking functions. One is based on NativeRank, and the other is based on BM25." + }, + { + "objectID": "passage_uncertainty_evaluation.html#deploy-the-application", + "href": "passage_uncertainty_evaluation.html#deploy-the-application", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Deploy the application", + "text": "Deploy the application\nDeploy the application package in a Docker container for local development. Alternatively, it is possible to deploy the application package to Vespa Cloud.\n\nfrom vespa.deployment import VespaDocker\n\nvespa_docker = VespaDocker()\napp = vespa_docker.deploy(application_package=app_package)\n\nWaiting for configuration server, 0/300 seconds...\nWaiting for configuration server, 5/300 seconds...\nWaiting for application status, 0/300 seconds...\nWaiting for application status, 5/300 seconds...\nWaiting for application status, 10/300 seconds...\nWaiting for application status, 15/300 seconds...\nWaiting for application status, 20/300 seconds...\nFinished deployment.\n\n\nOnce the deployment is finished, we can interact with the deployed application through the app variable." + }, + { + "objectID": "passage_uncertainty_evaluation.html#get-sample-data", + "href": "passage_uncertainty_evaluation.html#get-sample-data", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Get sample data", + "text": "Get sample data\nWe can load passage ranking sample data with PassageData.load. By default, it will download pre-generated sample data.\n\nfrom learntorank.passage import PassageData\n\ndata = PassageData.load()\n\n\ndata\n\nPassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)\n\n\n\ndata.summary\n\nNumber of documents: 1000\nNumber of train queries: 100\nNumber of train relevance judgments: 100\nNumber of dev queries: 100\nNumber of dev relevance judgments: 100" + }, + { + "objectID": "passage_uncertainty_evaluation.html#feed-the-application", + "href": "passage_uncertainty_evaluation.html#feed-the-application", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Feed the application", + "text": "Feed the application\nGet the document corpus in a DataFrame format.\n\ncorpus_df = data.get_corpus()\ncorpus_df.head()\n\n\n\n\n\n\n\n\ndoc_id\ntext\n\n\n\n\n0\n5954248\nWhy GameStop is excited for Dragon Age: Inquis...\n\n\n1\n7290700\nmetaplasia definition: 1. abnormal change of o...\n\n\n2\n5465518\nCandice Net Worth. According to the report of ...\n\n\n3\n3100518\nUnder the Base Closure Act, March AFB was down...\n\n\n4\n3207764\nThere are a number of career opportunities for...\n\n\n\n\n\n\n\nFeed the data to the deployed application.\n\nresponses = app.feed_df(df=corpus_df, include_id=True, id_field=\"doc_id\")\n\nSuccessful documents fed: 1000/1000.\nBatch progress: 1/1.\n\n\nWe can also check the number of successfully fed documents through the responses status code:\n\nsum([response.status_code == 200 for response in responses])\n\n1000" + }, + { + "objectID": "passage_uncertainty_evaluation.html#query-the-application", + "href": "passage_uncertainty_evaluation.html#query-the-application", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Query the application", + "text": "Query the application\nGet the dev set queries in a DataFrame format.\n\ndev_queries_df = data.get_queries(type=\"dev\")\ndev_queries_df.head()\n\n\n\n\n\n\n\n\nquery_id\nquery\n\n\n\n\n0\n1101971\nwhy say the sky is the limit\n\n\n1\n712898\nwhat is an cvc in radiology\n\n\n2\n154469\ndmv california how long does it take to get id\n\n\n3\n930015\nwhat's an epigraph\n\n\n4\n860085\nwhat is va tax\n\n\n\n\n\n\n\nGet the first query text to use as an example when querying our passage search application.\n\nsample_query = dev_queries_df.loc[0, \"query\"]\nsample_query\n\n'why say the sky is the limit'\n\n\n\nQuery with QueryModel\nCreate the bm25 QueryModel, which uses Vespa’s weakAnd operator to match documents relevant to the query and use the bm25 rank-profile that we defined in the application package above to rank the documents.\n\nfrom learntorank.query import QueryModel, WeakAnd, Ranking\n\nbm25_query_model = QueryModel(\n name=\"bm25\", \n match_phase=WeakAnd(hits=100), \n ranking=Ranking(name=\"bm25\")\n)\n\nOnce a QueryModel is specified, we can use it to query our application.\n\nfrom learntorank.query import send_query\nfrom pprint import pprint\n\nresponse = send_query(\n app=app,\n query=sample_query, \n query_model=bm25_query_model\n)\npprint(response.hits[0:2])\n\n[{'fields': {'doc_id': '7407715',\n 'documentid': 'id:PassageRanking:PassageRanking::7407715',\n 'sddocname': 'PassageRanking',\n 'summaryfeatures': {'bm25(text)': 11.979235042476953,\n 'vespa.summaryFeatures.cached': 0.0},\n 'text': 'The Sky is the Limit also known as TSITL is a global '\n 'effort designed to influence, motivate and inspire '\n 'people all over the world to achieve their goals and '\n 'dreams in life. TSITL’s collaborative community on '\n 'social media provides you with a vast archive of '\n 'motivational pictures/quotes/videos.'},\n 'id': 'id:PassageRanking:PassageRanking::7407715',\n 'relevance': 11.979235042476953,\n 'source': 'PassageRanking_content'},\n {'fields': {'doc_id': '84721',\n 'documentid': 'id:PassageRanking:PassageRanking::84721',\n 'sddocname': 'PassageRanking',\n 'summaryfeatures': {'bm25(text)': 11.310323797415357,\n 'vespa.summaryFeatures.cached': 0.0},\n 'text': 'Sky Customer Service 0870 280 2564. Use the Sky contact '\n 'number to get in contact with the Sky customer services '\n 'team to speak to a representative about your Sky TV, Sky '\n 'Internet or Sky telephone services. The Sky customer '\n 'Services team is operational between 8:30am and 11:30pm '\n 'seven days a week.'},\n 'id': 'id:PassageRanking:PassageRanking::84721',\n 'relevance': 11.310323797415357,\n 'source': 'PassageRanking_content'}]\n\n\n\n\nQuery with Vespa Query Language\nWe can also translate the query created with the QueryModel into the Vespa Query Language (YQL) by setting debug_request=True:\n\nresponse = send_query(\n app=app,\n query = sample_query, \n query_model=bm25_query_model, \n debug_request=True\n)\nyql_body = response.request_body\npprint(yql_body)\n\n{'ranking': {'listFeatures': 'false', 'profile': 'bm25'},\n 'yql': 'select * from sources * where ({targetHits: 100}weakAnd(default '\n 'contains \"why\", default contains \"say\", default contains \"the\", '\n 'default contains \"sky\", default contains \"is\", default contains '\n '\"the\", default contains \"limit\"));'}\n\n\nWe can use Vespa YQL directly via the body parameter:\n\nyql_response = send_query(app=app, body=yql_body)\npprint(yql_response.hits[0:2])\n\n[{'fields': {'doc_id': '7407715',\n 'documentid': 'id:PassageRanking:PassageRanking::7407715',\n 'sddocname': 'PassageRanking',\n 'summaryfeatures': {'bm25(text)': 11.979235042476953,\n 'vespa.summaryFeatures.cached': 0.0},\n 'text': 'The Sky is the Limit also known as TSITL is a global '\n 'effort designed to influence, motivate and inspire '\n 'people all over the world to achieve their goals and '\n 'dreams in life. TSITL’s collaborative community on '\n 'social media provides you with a vast archive of '\n 'motivational pictures/quotes/videos.'},\n 'id': 'id:PassageRanking:PassageRanking::7407715',\n 'relevance': 11.979235042476953,\n 'source': 'PassageRanking_content'},\n {'fields': {'doc_id': '84721',\n 'documentid': 'id:PassageRanking:PassageRanking::84721',\n 'sddocname': 'PassageRanking',\n 'summaryfeatures': {'bm25(text)': 11.310323797415357,\n 'vespa.summaryFeatures.cached': 0.0},\n 'text': 'Sky Customer Service 0870 280 2564. Use the Sky contact '\n 'number to get in contact with the Sky customer services '\n 'team to speak to a representative about your Sky TV, Sky '\n 'Internet or Sky telephone services. The Sky customer '\n 'Services team is operational between 8:30am and 11:30pm '\n 'seven days a week.'},\n 'id': 'id:PassageRanking:PassageRanking::84721',\n 'relevance': 11.310323797415357,\n 'source': 'PassageRanking_content'}]" + }, + { + "objectID": "passage_uncertainty_evaluation.html#evaluate-query-models", + "href": "passage_uncertainty_evaluation.html#evaluate-query-models", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Evaluate query models", + "text": "Evaluate query models\nIn this section, we want to evaluate and compare the bm25_query_model defined above with the native_query_model defined below:\n\nnative_query_model = QueryModel(\n name=\"native_rank\", \n match_phase=WeakAnd(hits=100), \n ranking=Ranking(name=\"native_rank\")\n)\n\nWe specify three metrics to evaluate the models.\n\nfrom learntorank.evaluation import (\n Recall, \n ReciprocalRank, \n NormalizedDiscountedCumulativeGain\n)\n\nmetrics = [\n Recall(at=10), \n ReciprocalRank(at=3), \n NormalizedDiscountedCumulativeGain(at=3)\n]\n\n\nPoint estimates\nIt is straightforward to obtain point estimates of the evaluation metrics for each query model being compared. In this case, we computed the mean and the standard deviation for each of the metrics.\n\nfrom learntorank.evaluation import evaluate\n\nevaluation = evaluate(\n app=app,\n labeled_data=data.get_labels(type=\"dev\"), \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n aggregators=[\"mean\", \"std\"]\n )\n\n\nevaluation\n\n\n\n\n\n\n\n\nmodel\nbm25\nnative_rank\n\n\n\n\nrecall_10\nmean\n0.935833\n0.845833\n\n\nstd\n0.215444\n0.342749\n\n\nreciprocal_rank_3\nmean\n0.935000\n0.755000\n\n\nstd\n0.231977\n0.394587\n\n\nndcg_3\nmean\n0.912839\n0.749504\n\n\nstd\n0.242272\n0.381792\n\n\n\n\n\n\n\nGiven the nature of the data distribution of the metrics described above, it is not trivial to compute a confidence interval from the mean and the standard deviation computed above. In the next section, we solve this by using bootstrap sampling on a per query metric evaluation.\n\n\nUncertainty estimates\nInstead of returning aggregated point estimates, we can also compute the metrics per query by setting per_query=True. This gives us more granular information on the distribution function of the metrics.\n\nevaluation_per_query = evaluate(\n app=app,\n labeled_data=data.get_labels(type=\"dev\"), \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n per_query=True\n)\n\n\nevaluation_per_query.head()\n\n\n\n\n\n\n\n\nmodel\nquery_id\nrecall_10\nreciprocal_rank_3\nndcg_3\n\n\n\n\n0\nnative_rank\n1101971\n1.0\n1.0\n1.0\n\n\n1\nnative_rank\n712898\n0.0\n0.0\n0.0\n\n\n2\nnative_rank\n154469\n1.0\n0.0\n0.0\n\n\n3\nnative_rank\n930015\n1.0\n1.0\n1.0\n\n\n4\nnative_rank\n860085\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\nWe then created a function that uses the evaluation per query data and computes uncertainty estimates via bootstrap sampling.\n\nfrom learntorank.stats import compute_evaluation_estimates\n\nestimates = compute_evaluation_estimates(\n df = evaluation_per_query\n)\n\n\nestimates\n\n\n\n\n\n\n\n\nmetric\nmodel\nlow\nmedian\nhigh\n\n\n\n\n0\nndcg_3\nbm25\n0.865279\n0.914863\n0.958766\n\n\n1\nndcg_3\nnative_rank\n0.675220\n0.752400\n0.820318\n\n\n2\nrecall_10\nbm25\n0.891667\n0.935833\n0.974187\n\n\n3\nrecall_10\nnative_rank\n0.778312\n0.847917\n0.910000\n\n\n4\nreciprocal_rank_3\nbm25\n0.890000\n0.935000\n0.975000\n\n\n5\nreciprocal_rank_3\nnative_rank\n0.678333\n0.758333\n0.831667\n\n\n\n\n\n\n\nWe can then create plots based on this data to make it easier to judge the magnitude of the differences between ranking functions.\n\nfrom plotnine import *\n\nprint((ggplot(estimates) + \n geom_point(aes(\"model\", \"median\")) + \n geom_errorbar(aes(x=\"model\", ymin=\"low\",ymax=\"high\")) + \n facet_wrap(\"metric\") + labs(y=\"Metric value\")\n))" + }, + { + "objectID": "passage_uncertainty_evaluation.html#cleanup-the-environment", + "href": "passage_uncertainty_evaluation.html#cleanup-the-environment", + "title": "IR evaluation metrics with uncertainty estimates", + "section": "Cleanup the environment", + "text": "Cleanup the environment\n\nvespa_docker.container.stop(timeout=600)\nvespa_docker.container.remove()" + }, + { + "objectID": "notebooks/compare-pre-trained-clip-for-text-image-search.html", + "href": "notebooks/compare-pre-trained-clip-for-text-image-search.html", + "title": "Compare pre-trained CLIP models for text-image retrieval", + "section": "", + "text": "Get the Vespa sample application, install requirements:\n! git clone --depth 1 https://github.com/vespa-engine/sample-apps.git\n\n! pip install -r requirements.txt\nThere are multiple CLIP model variations:\nimport clip\n\nclip.available_models()\nTo limit run-time of this notebook, do not use all models - modify the set:\n# Limit number of models to test - here, first two models\nuse_models = clip.available_models()[0:2]\nEach model has an embedding size, needed in the text-image search application schema:\nembedding_info = {name: clip.load(name)[0].visual.output_dim for name in use_models}\nembedding_info" + }, + { + "objectID": "notebooks/compare-pre-trained-clip-for-text-image-search.html#create-and-deploy-a-text-image-search-app", + "href": "notebooks/compare-pre-trained-clip-for-text-image-search.html#create-and-deploy-a-text-image-search-app", + "title": "Compare pre-trained CLIP models for text-image retrieval", + "section": "Create and deploy a text-image search app", + "text": "Create and deploy a text-image search app\n\nCreate the Vespa application package\nThe function create_text_image_app below uses the Vespa python API to create an application package with fields to store each of the different types of image embedding associated with the CLIP models. It also declares the types of the text embeddings that we are going to send along with the query when searching for images, and creates one ranking profile for each (text, image) embedding model:\n\nfrom embedding import create_text_image_app\n\napp_package = create_text_image_app(embedding_info)\n\nInspect the schema of the resulting application package:\n\nprint(app_package.schema.schema_to_text)\n\n\n\nDeploy\n\nimport os\nfrom vespa.deployment import VespaDocker\n\nvespa_docker = VespaDocker()\napp = vespa_docker.deploy(application_package=app_package)" + }, + { + "objectID": "notebooks/compare-pre-trained-clip-for-text-image-search.html#compute-and-feed-image-embeddings", + "href": "notebooks/compare-pre-trained-clip-for-text-image-search.html#compute-and-feed-image-embeddings", + "title": "Compare pre-trained CLIP models for text-image retrieval", + "section": "Compute and feed image embeddings", + "text": "Compute and feed image embeddings\nGet a sample data set. See download_flickr8k.sh for how to download images. Set location of images:\nFor each of the CLIP models, compute the image embeddings and send it to the Vespa app:\n\nfrom embedding import compute_and_send_image_embeddings\n\ncompute_and_send_image_embeddings(app=app, batch_size=128, clip_model_names=use_models)" + }, + { + "objectID": "notebooks/compare-pre-trained-clip-for-text-image-search.html#define-querymodels-to-be-evaluated", + "href": "notebooks/compare-pre-trained-clip-for-text-image-search.html#define-querymodels-to-be-evaluated", + "title": "Compare pre-trained CLIP models for text-image retrieval", + "section": "Define QueryModel’s to be evaluated", + "text": "Define QueryModel’s to be evaluated\nCreate one QueryModel for each of the CLIP models. In order to do that, we need to have a function that takes a query as input and outputs the body function of a Vespa query request - example:\n\nfrom embedding import create_vespa_query_body_function\n\nvespa_query_body_function = create_vespa_query_body_function(\"RN50\")\nvespa_query_body_function(\"this is a test query\")[\"yql\"]\n\nWith a method to create Vespa query body functions, we can create QueryModels that will be used to evaluate each search configuration that is to be tested. In this case, each query model will represent a CLIP model text-image representation:\n\nfrom learntorank.query import QueryModel\n\nquery_models = [QueryModel(\n name=model_name, \n body_function=create_vespa_query_body_function(model_name)\n) for model_name in use_models]\n\nA query model contains all the information that is necessary to define how the search app will match and rank documents. Use it to query the application:\n\nfrom embedding import plot_images\nfrom learntorank.query import send_query\n\nquery_result = send_query(app, query=\"a person surfing\", query_model=query_models[-1], hits = 4)\n\nTo inspect the results, use query_result.hits[0]. Display top two:\n\nfrom IPython.display import Image, display\n\nimage_file_names = [ hit[\"fields\"][\"image_file_name\"] for hit in query_result.hits[:2] ]\n\nfor image in image_file_names:\n display(Image(filename=os.path.join(os.environ[\"IMG_DIR\"], image)))" + }, + { + "objectID": "notebooks/compare-pre-trained-clip-for-text-image-search.html#evaluate", + "href": "notebooks/compare-pre-trained-clip-for-text-image-search.html#evaluate", + "title": "Compare pre-trained CLIP models for text-image retrieval", + "section": "Evaluate", + "text": "Evaluate\nNow that there is one QueryModel for each CLIP model available, it is posible to evaluate and compare them.\nDefine search evaluation metrics:\n\nfrom learntorank.evaluation import MatchRatio, Recall, ReciprocalRank\n\neval_metrics = [\n MatchRatio(), # Match ratio is just to show the % of documents that are matched by ANN\n Recall(at=100), \n ReciprocalRank(at=100)\n]\n\nLoad labeled data. It was assumed that a (caption, image) pair is relevant if all three experts agreed that the caption accurately described the image:\n\nfrom pandas import read_csv\n\nlabeled_data = read_csv(\"https://data.vespa.oath.cloud/blog/flickr8k/labeled_data.csv\", sep = \"\\t\")\nlabeled_data.head()\n\nEvaluate the application and return per query results:\n\nfrom learntorank.evaluation import evaluate\n\nresult = evaluate(\n app=app,\n labeled_data=labeled_data, \n eval_metrics=eval_metrics, \n query_model=query_models, \n id_field=\"image_file_name\",\n per_query=True\n)\nresult.head()\n\nVisualize RR@100:\n\nimport matplotlib.pyplot as plt\n\nplt.plot(result.reciprocal_rank_100)\nplt.ylabel(\"reciprocal_rank_100\")\nplt.show()\n\nCompute mean and median across models:\n\nresult[[\"model\", \"reciprocal_rank_100\"]].groupby(\n \"model\"\n).agg(\n Mean=('reciprocal_rank_100', 'mean'), \n Median=('reciprocal_rank_100', 'median')\n)" + }, + { + "objectID": "notebooks/compare-pre-trained-clip-for-text-image-search.html#cleanup", + "href": "notebooks/compare-pre-trained-clip-for-text-image-search.html#cleanup", + "title": "Compare pre-trained CLIP models for text-image retrieval", + "section": "Cleanup", + "text": "Cleanup\nStop and remove the Docker container:\n\nvespa_docker.container.stop()\nvespa_docker.container.remove()" + }, + { + "objectID": "notebooks/cord19/cord19_connect_evaluate.html", + "href": "notebooks/cord19/cord19_connect_evaluate.html", + "title": "How to evaluate Vespa ranking functions from python", + "section": "", + "text": "We can start by downloading the data that we have processed before.\n\nimport requests, json\nfrom pandas import read_csv\n\ntopics = json.loads(\n requests.get(\"https://thigm85.github.io/data/cord19/topics.json\").text\n)\nrelevance_data = read_csv(\"https://thigm85.github.io/data/cord19/relevance_data.csv\")\n\ntopics contain data about the 50 topics available, including query, question and narrative.\n\ntopics[\"1\"]\n\n{'query': 'coronavirus origin',\n 'question': 'what is the origin of COVID-19',\n 'narrative': \"seeking range of information about the SARS-CoV-2 virus's origin, including its evolution, animal source, and first transmission into humans\"}\n\n\nrelevance_data contains the relevance judgments for each of the 50 topics.\n\nrelevance_data.head(5)\n\n\n\n\n\n\n\n\ntopic_id\nround_id\ncord_uid\nrelevancy\n\n\n\n\n0\n1\n4.5\n005b2j4b\n2\n\n\n1\n1\n4.0\n00fmeepz\n1\n\n\n2\n1\n0.5\n010vptx3\n2\n\n\n3\n1\n2.5\n0194oljo\n1\n\n\n4\n1\n4.0\n021q9884\n1" + }, + { + "objectID": "notebooks/cord19/cord19_connect_evaluate.html#download-processed-data", + "href": "notebooks/cord19/cord19_connect_evaluate.html#download-processed-data", + "title": "How to evaluate Vespa ranking functions from python", + "section": "", + "text": "We can start by downloading the data that we have processed before.\n\nimport requests, json\nfrom pandas import read_csv\n\ntopics = json.loads(\n requests.get(\"https://thigm85.github.io/data/cord19/topics.json\").text\n)\nrelevance_data = read_csv(\"https://thigm85.github.io/data/cord19/relevance_data.csv\")\n\ntopics contain data about the 50 topics available, including query, question and narrative.\n\ntopics[\"1\"]\n\n{'query': 'coronavirus origin',\n 'question': 'what is the origin of COVID-19',\n 'narrative': \"seeking range of information about the SARS-CoV-2 virus's origin, including its evolution, animal source, and first transmission into humans\"}\n\n\nrelevance_data contains the relevance judgments for each of the 50 topics.\n\nrelevance_data.head(5)\n\n\n\n\n\n\n\n\ntopic_id\nround_id\ncord_uid\nrelevancy\n\n\n\n\n0\n1\n4.5\n005b2j4b\n2\n\n\n1\n1\n4.0\n00fmeepz\n1\n\n\n2\n1\n0.5\n010vptx3\n2\n\n\n3\n1\n2.5\n0194oljo\n1\n\n\n4\n1\n4.0\n021q9884\n1" + }, + { + "objectID": "notebooks/cord19/cord19_connect_evaluate.html#format-the-labeled-data-into-expected-pyvespa-format", + "href": "notebooks/cord19/cord19_connect_evaluate.html#format-the-labeled-data-into-expected-pyvespa-format", + "title": "How to evaluate Vespa ranking functions from python", + "section": "Format the labeled data into expected pyvespa format", + "text": "Format the labeled data into expected pyvespa format\npyvespa expects labeled data to follow the format illustrated below. It is a list of dict where each dict represents a query containing query_id, query and a list of relevant_docs. Each relevant document contains a required id key and an optional score key.\n\nlabeled_data = [\n {\n 'query_id': 1,\n 'query': 'coronavirus origin',\n 'relevant_docs': [{'id': '005b2j4b', 'score': 2}, {'id': '00fmeepz', 'score': 1}]\n },\n {\n 'query_id': 2,\n 'query': 'coronavirus response to weather changes',\n 'relevant_docs': [{'id': '01goni72', 'score': 2}, {'id': '03h85lvy', 'score': 2}]\n }\n]\n\nWe can create labeled_data from the topics and relevance_data that we downloaded before. We are only going to include documents with relevance score > 0 into the final list.\n\nlabeled_data = [\n {\n \"query_id\": int(topic_id), \n \"query\": topics[topic_id][\"query\"], \n \"relevant_docs\": [\n {\n \"id\": row[\"cord_uid\"], \n \"score\": row[\"relevancy\"]\n } for idx, row in relevance_data[relevance_data.topic_id == int(topic_id)].iterrows() if row[\"relevancy\"] > 0\n ]\n } for topic_id in topics.keys()]" + }, + { + "objectID": "notebooks/cord19/cord19_connect_evaluate.html#define-query-models-to-be-evaluated", + "href": "notebooks/cord19/cord19_connect_evaluate.html#define-query-models-to-be-evaluated", + "title": "How to evaluate Vespa ranking functions from python", + "section": "Define query models to be evaluated", + "text": "Define query models to be evaluated\nWe are going to define two query models to be evaluated here. Both will match all the documents that share at least one term with the query. This is defined by setting match_phase = OR().\nThe difference between the query models happens in the ranking phase. The or_default model will rank documents based on nativeRank while the or_bm25 model will rank documents based on BM25. Discussion about those two types of ranking is out of the scope of this tutorial. It is enough to know that they rank documents according to two different formulas.\nThose ranking profiles were defined by the team behind the cord19 app and can be found here.\n\nfrom learntorank.query import QueryModel, Ranking, OR\n\nquery_models = [\n QueryModel(\n name=\"or_default\",\n match_phase = OR(),\n ranking = Ranking(name=\"default\")\n ),\n QueryModel(\n name=\"or_bm25\",\n match_phase = OR(),\n ranking = Ranking(name=\"bm25t5\")\n )\n]" + }, + { + "objectID": "notebooks/cord19/cord19_connect_evaluate.html#define-metrics-to-be-used-in-the-evaluation", + "href": "notebooks/cord19/cord19_connect_evaluate.html#define-metrics-to-be-used-in-the-evaluation", + "title": "How to evaluate Vespa ranking functions from python", + "section": "Define metrics to be used in the evaluation", + "text": "Define metrics to be used in the evaluation\nWe would like to compute the following metrics:\n\nThe percentage of documents matched by the query\nRecall @ 10\nReciprocal rank @ 10\nNDCG @ 10\n\n\nfrom learntorank.evaluation import MatchRatio, Recall, ReciprocalRank, NormalizedDiscountedCumulativeGain\n\neval_metrics = [\n MatchRatio(), \n Recall(at=10), \n ReciprocalRank(at=10), \n NormalizedDiscountedCumulativeGain(at=10)\n]" + }, + { + "objectID": "notebooks/cord19/cord19_connect_evaluate.html#evaluate", + "href": "notebooks/cord19/cord19_connect_evaluate.html#evaluate", + "title": "How to evaluate Vespa ranking functions from python", + "section": "Evaluate", + "text": "Evaluate\nConnect to a running Vespa instance:\n\nfrom vespa.application import Vespa\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\n\nCompute the metrics defined above for each query model.\n\nfrom learntorank.evaluation import evaluate\nevaluations = evaluate(\n app=app,\n labeled_data = labeled_data,\n eval_metrics = eval_metrics,\n query_model = query_models,\n id_field = \"cord_uid\",\n hits = 10\n)\nevaluations\n\n\n\n\n\n\n\n\nmodel\nor_bm25\nor_default\n\n\n\n\nmatch_ratio\nmean\n0.411789\n0.411789\n\n\nmedian\n0.282227\n0.282227\n\n\nstd\n0.238502\n0.238502\n\n\nrecall_10\nmean\n0.007720\n0.005457\n\n\nmedian\n0.006089\n0.003753\n\n\nstd\n0.006386\n0.005458\n\n\nreciprocal_rank_10\nmean\n0.594357\n0.561579\n\n\nmedian\n0.500000\n0.500000\n\n\nstd\n0.397597\n0.401255\n\n\nndcg_10\nmean\n0.353095\n0.274515\n\n\nmedian\n0.355978\n0.253619\n\n\nstd\n0.216460\n0.203170\n\n\n\n\n\n\n\nWe can also return per query raw evaluation metrics:\n\nevaluations = evaluate(\n app=app,\n labeled_data = labeled_data,\n eval_metrics = eval_metrics,\n query_model = query_models,\n id_field = \"cord_uid\",\n hits = 10,\n per_query = True\n)\nevaluations.head()\n\n\n\n\n\n\n\n\nmodel\nquery_id\nmatch_ratio\nrecall_10\nreciprocal_rank_10\nndcg_10\n\n\n\n\n0\nor_default\n1\n0.230847\n0.008584\n1.000000\n0.519431\n\n\n1\nor_default\n2\n0.755230\n0.000000\n0.000000\n0.000000\n\n\n2\nor_default\n3\n0.264601\n0.001534\n0.142857\n0.036682\n\n\n3\nor_default\n4\n0.843341\n0.001764\n0.333333\n0.110046\n\n\n4\nor_default\n5\n0.901317\n0.003096\n0.250000\n0.258330" + }, + { + "objectID": "notebooks/image_search/image-search-scratch.html", + "href": "notebooks/image_search/image-search-scratch.html", + "title": "Image search", + "section": "", + "text": "This notebook walks through the pyvespa code used to create the text to image search sample application." + }, + { + "objectID": "notebooks/image_search/image-search-scratch.html#create-the-application-package", + "href": "notebooks/image_search/image-search-scratch.html#create-the-application-package", + "title": "Image search", + "section": "Create the application package", + "text": "Create the application package\nCreate an application package:\n\nfrom vespa.package import ApplicationPackage\n\napp_package = ApplicationPackage(name=\"imagesearch\")\n\nAdd a field to hold the name of the image file. This is used in the sample app to load the final images that should be displayed to the end user.\nThe summary indexing ensures this field is returned as part of the query response. The attribute indexing store the fields in memory as an attribute for sorting, querying, and grouping:\n\nfrom vespa.package import Field\n\napp_package.schema.add_fields(\n Field(name=\"image_file_name\", type=\"string\", indexing=[\"summary\", \"attribute\"]),\n)\n\nAdd a field to hold an image embedding. The embeddings are usually generated by a ML model. We can add multiple embedding fields to our application. This is useful when making experiments. For example, the sample app adds 6 image embeddings, one for each of the six pre-trained CLIP models available at the time.\nIn the example below, the embedding vector has size 512 and is of type float. The index is required to enable approximate matching and the HNSW instance configure the HNSW index:\n\nfrom vespa.package import HNSW\n\napp_package.schema.add_fields(\n Field(\n name=\"embedding_image\",\n type=\"tensor<float>(x[512])\",\n indexing=[\"attribute\", \"index\"],\n ann=HNSW(\n distance_metric=\"angular\",\n max_links_per_node=16,\n neighbors_to_explore_at_insert=500,\n ),\n )\n)\n\nAdd a rank profile that ranks the images by how close the image embedding vector is from the query embedding vector. The tensors used in queries must have their type declared in the application package, the code below declares the text embedding that will be sent in the query - it has the same size and type of the image embedding:\n\nfrom vespa.package import RankProfile\n\napp_package.schema.add_rank_profile(\n RankProfile(\n name=\"embedding_similarity\",\n inherits=\"default\",\n first_phase=\"closeness(embedding_image)\",\n inputs=[(\"query(embedding_text)\", \"tensor<float>(x[512])\")],\n )\n)" + }, + { + "objectID": "notebooks/image_search/image-search-scratch.html#deploy-the-application", + "href": "notebooks/image_search/image-search-scratch.html#deploy-the-application", + "title": "Image search", + "section": "Deploy the application", + "text": "Deploy the application\nThe application package created above can be deployed using Docker or Vespa Cloud. Follow the instructions based on the desired deployment mode. Either option will create a Vespa connection instance that can be stored in a variable that will be denoted here as app.\nWe can then use app to interact with the deployed application:\n\nimport os\nfrom vespa.deployment import VespaDocker\n\nvespa_docker = VespaDocker(\n port=8080\n)\n\napp = vespa_docker.deploy(application_package = app_package)\n\nWaiting for configuration server, 0/300 seconds...\nWaiting for configuration server, 5/300 seconds...\nWaiting for application status, 0/300 seconds...\nWaiting for application status, 5/300 seconds...\nWaiting for application status, 10/300 seconds...\nWaiting for application status, 15/300 seconds...\nWaiting for application status, 20/300 seconds...\nWaiting for application status, 25/300 seconds...\nFinished deployment." + }, + { + "objectID": "notebooks/image_search/image-search-scratch.html#feed-the-image-data", + "href": "notebooks/image_search/image-search-scratch.html#feed-the-image-data", + "title": "Image search", + "section": "Feed the image data", + "text": "Feed the image data\nToDo: Add code below to create the feed and set batch - until then, disabled auto testing.\nTo feed the image data:\n\nresponses = app.feed_batch(batch)\n\nwhere batch is a list of dictionaries like the one below:\n\n{\n \"id\": \"dog1\",\n \"fields\": {\n \"image_file_name\": \"dog1.jpg\",\n \"embedding_image\": {\"values\": [0.884, -0.345, ..., 0.326]},\n }\n}\n\nOne of the advantages of having a python API is that it can integrate with commonly used ML frameworks. The sample application show how to create a PyTorch DataLoader to generate batches of image data by using CLIP models to generate image embeddings." + }, + { + "objectID": "notebooks/image_search/image-search-scratch.html#query-the-application", + "href": "notebooks/image_search/image-search-scratch.html#query-the-application", + "title": "Image search", + "section": "Query the application", + "text": "Query the application\nThe following query will use approximate nearest neighbor search to match the closest images to the query text and rank the images according to their distance to the query text. The sample application used CLIP models to generate image and query embeddings.\n\nresponse = app.query(body={\n \"yql\": 'select * from sources * where ({targetHits:100}nearestNeighbor(embedding_image,embedding_text));',\n \"hits\": 100,\n \"input.query(embedding_text)\": [0.632, -0.987, ..., 0.534],\n \"ranking.profile\": \"embedding_similarity\"\n})" + }, + { + "objectID": "notebooks/image_search/image-search-scratch.html#evaluate-different-query-models", + "href": "notebooks/image_search/image-search-scratch.html#evaluate-different-query-models", + "title": "Image search", + "section": "Evaluate different query models", + "text": "Evaluate different query models\nDefine metrics to evaluate:\n\nfrom learntorank.evaluation import MatchRatio, Recall, ReciprocalRank\n\neval_metrics = [\n MatchRatio(), \n Recall(at=100), \n ReciprocalRank(at=100)\n]\n\nThe sample application illustrates how to evaluate different CLIP models through the evaluate method:\n\nresult = app.evaluate(\n labeled_data=labeled_data, # Labeled data to define which images should be returned to a given query\n eval_metrics=eval_metrics, # Metrics used\n query_model=query_models, # Each query model uses a different CLIP model version\n id_field=\"image_file_name\", # The name of the id field used by the labeled data to identify the image\n per_query=True # Return results per query rather the aggragated.\n)\n\nThe figure below is the reciprocal rank at 100 computed based on the output of the evaluate method.\n\n\n\nevaluation" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html", + "href": "notebooks/tensorflow-via-onnx.html", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "", + "text": "This tutorial will cover the following steps:" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#install-packages", + "href": "notebooks/tensorflow-via-onnx.html#install-packages", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Install packages", + "text": "Install packages\n\n!pip3 install -Uqq pyvespa learntorank numpy==1.23.5 pandas tensorflow tensorflow_ranking onnx tf2onnx" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#get-the-data", + "href": "notebooks/tensorflow-via-onnx.html#get-the-data", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Get the data", + "text": "Get the data\n\nimport pandas as pd\n\nDownload labeled data containing Vespa ranking features collected from an MS Marco passage ranking application.\n\ndf = pd.read_csv(\"https://data.vespa.oath.cloud/blog/ranking/train_sample.csv\")\ndf = df[\n [\"document_id\", \n \"query_id\", \n \"label\", \n \"fieldMatch(body).queryCompleteness\",\n \"fieldMatch(body).significance\",\n \"nativeRank\",\n ]\n]\n\n\ndf.shape\n\n(100000, 6)\n\n\nFor each query_id, there is 9 irrelevant document_id with label = 0 and 1 relevant document_id with label = 1.\n\ndf.head(10)\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nfieldMatch(body).queryCompleteness\nfieldMatch(body).significance\nnativeRank\n\n\n\n\n0\n27061\n3\n0\n0.625\n0.566311\n0.042421\n\n\n1\n257\n3\n0\n0.625\n0.582570\n0.039192\n\n\n2\n363\n3\n0\n0.500\n0.466030\n0.034418\n\n\n3\n22682\n3\n0\n0.625\n0.566311\n0.061149\n\n\n4\n160\n3\n0\n0.500\n0.437808\n0.035017\n\n\n5\n228\n3\n0\n0.500\n0.437808\n0.032697\n\n\n6\n3901893\n3\n0\n0.750\n0.748064\n0.074917\n\n\n7\n1142680\n3\n1\n0.750\n0.748064\n0.099112\n\n\n8\n141\n3\n0\n0.500\n0.442879\n0.038093\n\n\n9\n3060834\n3\n0\n0.750\n0.763933\n0.075347" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#create-a-listwise-dataset", + "href": "notebooks/tensorflow-via-onnx.html#create-a-listwise-dataset", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Create a listwise dataset", + "text": "Create a listwise dataset\nDefine some parameters required to setup the listwise data pipeline.\n\nnumber_documents_per_query = 10 \nfeature_names = [ \n \"fieldMatch(body).queryCompleteness\", \n \"fieldMatch(body).significance\", \n \"nativeRank\"\n]\nnumber_features = len(feature_names)\nbatch_size=32\n\nEach feature data point will have the shape equal to (batch_size, number_documents_per_query, number_features) and each label data point will have shape equal to (batch_size, number_documents_per_query).\n\nimport tensorflow as tf\n\nThe code below creates a TensorFlow data pipeline (tf.data.Dataset) from our DataFrame and group the rows by the query_id variable to form a listwise dataset. We then configure the data pipeline to shuffle and set a batch size.\n\nshuffle_buffer_size = 10000\nds = tf.data.Dataset.from_tensor_slices(\n {\n \"features\": tf.cast(df[feature_names].values, tf.float32),\n \"label\": tf.cast(df[\"label\"].values, tf.float32),\n \"query_id\": tf.cast(df[\"query_id\"].values, tf.int64),\n }\n)\n\nkey_func = lambda x: x[\"query_id\"]\nreduce_func = lambda key, dataset: dataset.batch(\n number_documents_per_query, drop_remainder=True\n)\nlistwise_ds = ds.group_by_window(\n key_func=key_func,\n reduce_func=reduce_func,\n window_size=number_documents_per_query,\n)\nlistwise_ds = listwise_ds.map(lambda x: (x[\"features\"], x[\"label\"]))\nlistwise_ds = listwise_ds.shuffle(buffer_size=shuffle_buffer_size).batch(\n batch_size=batch_size\n)\n\nWe can see the shape of the features and of the labels are as expected.\n\nfor d in listwise_ds.take(1):\n print(d[0].shape)\n print(d[1].shape)\n\n(32, 10, 3)\n(32, 10)" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#create-and-compile-model", + "href": "notebooks/tensorflow-via-onnx.html#create-and-compile-model", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Create and compile model", + "text": "Create and compile model\nWe are going to create a linear model that can take a listwise data as input with shape (batch_size, number_documents_per_query, number_features) and output one prediction per document with shape (batch_size, number_documents_per_query)\n\ninput_layer = tf.keras.layers.Input(shape=(number_documents_per_query, number_features))\ndense_layer = tf.keras.layers.Dense(\n 1,\n use_bias=False,\n activation=None,\n name=\"dense\"\n)\noutput_layer = tf.keras.layers.Reshape((number_documents_per_query,))\n\n\nmodel = tf.keras.Sequential(layers=[input_layer, dense_layer, output_layer])\n\nIn this tutorial, we want to optimize the Normalized Discounted Cumulative Gain at position 10 (NDCG@10). We then select a loss function that is a smooth approximation of the NDCG metric and create a stateless NDCG@10 metric to use when compiling the model defined above.\n\nimport tensorflow_ranking as tfr\n\nndcg = tfr.keras.metrics.NDCGMetric(topn=10)\ndef ndcg_stateless(y_true, y_pred):\n \"\"\"\n Create stateless metric so that we can compute the validation metric \n from scratch at the end of each epoch.\n \"\"\"\n ndcg.reset_states()\n return ndcg(y_true, y_pred)\n\noptimizer = tf.keras.optimizers.Adagrad(learning_rate=2)\nmodel.compile(\n optimizer=optimizer,\n loss=tfr.keras.losses.ApproxNDCGLoss(),\n metrics=ndcg_stateless,\n)\n\nUse the listwise dataset to fit the model:\n\nhistory = model.fit(listwise_ds, epochs=20)\n\nEpoch 1/20\n304/304 [==============================] - 8s 3ms/step - loss: -0.6522 - ndcg_stateless: 0.6874\nEpoch 2/20\n304/304 [==============================] - 1s 921us/step - loss: -0.6959 - ndcg_stateless: 0.7159\nEpoch 3/20\n304/304 [==============================] - 1s 905us/step - loss: -0.7001 - ndcg_stateless: 0.7166\nEpoch 4/20\n304/304 [==============================] - 1s 904us/step - loss: -0.7025 - ndcg_stateless: 0.7168\nEpoch 5/20\n304/304 [==============================] - 1s 901us/step - loss: -0.7043 - ndcg_stateless: 0.7165\nEpoch 6/20\n304/304 [==============================] - 1s 920us/step - loss: -0.7106 - ndcg_stateless: 0.7242\nEpoch 7/20\n304/304 [==============================] - 1s 903us/step - loss: -0.7355 - ndcg_stateless: 0.7647\nEpoch 8/20\n304/304 [==============================] - 1s 898us/step - loss: -0.7399 - ndcg_stateless: 0.7662\nEpoch 9/20\n304/304 [==============================] - 1s 923us/step - loss: -0.7430 - ndcg_stateless: 0.7679\nEpoch 10/20\n304/304 [==============================] - 1s 911us/step - loss: -0.7450 - ndcg_stateless: 0.7679\nEpoch 11/20\n304/304 [==============================] - 1s 955us/step - loss: -0.7464 - ndcg_stateless: 0.7682\nEpoch 12/20\n304/304 [==============================] - 1s 914us/step - loss: -0.7475 - ndcg_stateless: 0.7683\nEpoch 13/20\n304/304 [==============================] - 1s 919us/step - loss: -0.7485 - ndcg_stateless: 0.7689\nEpoch 14/20\n304/304 [==============================] - 1s 909us/step - loss: -0.7493 - ndcg_stateless: 0.7682\nEpoch 15/20\n304/304 [==============================] - 1s 904us/step - loss: -0.7499 - ndcg_stateless: 0.7692\nEpoch 16/20\n304/304 [==============================] - 1s 900us/step - loss: -0.7506 - ndcg_stateless: 0.7691\nEpoch 17/20\n304/304 [==============================] - 1s 893us/step - loss: -0.7513 - ndcg_stateless: 0.7699\nEpoch 18/20\n304/304 [==============================] - 1s 1ms/step - loss: -0.7516 - ndcg_stateless: 0.7694\nEpoch 19/20\n304/304 [==============================] - 1s 910us/step - loss: -0.7520 - ndcg_stateless: 0.7694\nEpoch 20/20\n304/304 [==============================] - 1s 830us/step - loss: -0.7524 - ndcg_stateless: 0.7686" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#simplify-model-inputoutput-for-deployment", + "href": "notebooks/tensorflow-via-onnx.html#simplify-model-inputoutput-for-deployment", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Simplify model input/output for deployment", + "text": "Simplify model input/output for deployment\nAfter training the model by minimizing a listwise loss function, we can simplify the model before deploying it to Vespa. At inference time, Vespa will evaluate each document individually and use a ranking function to rank documents.\nTherefore, the input layer will expect a tensor named input with shape equal to (1, number_features).\n\nsimpler_model = tf.keras.Sequential(\n [tf.keras.layers.Input(shape=(number_features,), batch_size=1, name=\"input\"), \n dense_layer\n ]\n)\n\nWe are going to save the simpler_model to disk and then use the tf2onnx tool to convert the model to ONNX format.\n\nsimpler_model.save(\"simpler_keras_model\")\n\nWARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.\nINFO:tensorflow:Assets written to: simpler_keras_model/assets\n\n\nINFO:tensorflow:Assets written to: simpler_keras_model/assets\n\n\n\nfrom tf2onnx import convert\n\n!python3 -m tf2onnx.convert --saved-model simpler_keras_model --output simpler_keras_model.onnx\n\n<frozen runpy>:128: RuntimeWarning: 'tf2onnx.convert' found in sys.modules after import of package 'tf2onnx', but prior to execution of 'tf2onnx.convert'; this may result in unpredictable behaviour\n2023-08-08 14:09:40,224 - WARNING - '--tag' not specified for saved_model. Using --tag serve\n2023-08-08 14:09:40,328 - INFO - Signatures found in model: [serving_default].\n2023-08-08 14:09:40,328 - WARNING - '--signature_def' not specified, using first signature: serving_default\n2023-08-08 14:09:40,328 - INFO - Output names: ['dense']\n2023-08-08 14:09:40,328 - WARNING - Could not search for non-variable resources. Concrete function internal representation may have changed.\nWARNING:tensorflow:From /usr/local/lib/python3.11/site-packages/tf2onnx/tf_loader.py:557: extract_sub_graph (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\nInstructions for updating:\nThis API was designed for TensorFlow v1. See https://www.tensorflow.org/guide/migrate for instructions on how to migrate your code to TensorFlow v2.\n2023-08-08 14:09:40,379 - WARNING - From /usr/local/lib/python3.11/site-packages/tf2onnx/tf_loader.py:557: extract_sub_graph (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\nInstructions for updating:\nThis API was designed for TensorFlow v1. See https://www.tensorflow.org/guide/migrate for instructions on how to migrate your code to TensorFlow v2.\n2023-08-08 14:09:40,388 - INFO - Using tensorflow=2.13.0, onnx=1.14.0, tf2onnx=1.8.4/cd55bf\n2023-08-08 14:09:40,388 - INFO - Using opset <onnx, 9>\n2023-08-08 14:09:40,389 - INFO - Computed 0 values for constant folding\n2023-08-08 14:09:40,395 - INFO - Optimizing ONNX model\n2023-08-08 14:09:40,402 - INFO - After optimization: Identity -5 (5->0)\n2023-08-08 14:09:40,403 - INFO - \n2023-08-08 14:09:40,403 - INFO - Successfully converted TensorFlow model simpler_keras_model to ONNX\n2023-08-08 14:09:40,403 - INFO - Model inputs: ['input:0']\n2023-08-08 14:09:40,403 - INFO - Model outputs: ['dense']\n2023-08-08 14:09:40,403 - INFO - ONNX model is saved at simpler_keras_model.onnx\n\n\nWe can inspect the onnx model input and output. We first load the ONNX model:\n\nimport onnx \n\nm = onnx.load(\"simpler_keras_model.onnx\")\n\nAs mentioned before, the model expects a tensor named input with shape (1, 3).\n\nm.graph.input\n\n[name: \"input\"\ntype {\n tensor_type {\n elem_type: 1\n shape {\n dim {\n dim_value: 1\n }\n dim {\n dim_value: 3\n }\n }\n }\n}\n]\n\n\nThe output will be a tensor named dense with shape (1,1).\n\nm.graph.output\n\n[name: \"dense\"\ntype {\n tensor_type {\n elem_type: 1\n shape {\n dim {\n dim_value: 1\n }\n dim {\n dim_value: 1\n }\n }\n }\n}\n]" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#define-the-application-package", + "href": "notebooks/tensorflow-via-onnx.html#define-the-application-package", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Define the application package", + "text": "Define the application package\nThis section will use the Vespa python API pyvespa to create an application package with a ranking function that uses the tensorflow model exported to ONNX.\nThe data used to train the model was derived from a Vespa application based on the MS Marco passage dataset. So, we are going to name the application msmarco, and start by adding two fields: id to hold the document id and text to hold the passages from the msmarco dataset.\nindexing configuration: We add \"summary\" to the indexing parameter because we want to include both the id and the text field in the query results. The \"attribute\" indicates that the field id will be stored in-memory. The \"index\" indicates that Vespa will create a search index for the text field.\n\nfrom vespa.package import ApplicationPackage, Field\n\napp_package = ApplicationPackage(name=\"msmarco\")\n\napp_package.schema.add_fields(\n Field(name=\"id\", type=\"string\", indexing=[\"summary\", \"attribute\"]),\n Field(name=\"text\", type=\"string\", indexing=[\"summary\", \"index\"])\n)\n\nNote that at each step along the application package definition, we can inspect the content of the Vespa search definition file:\n\nprint(app_package.schema.schema_to_text)\n\nschema msmarco {\n document msmarco {\n field id type string {\n indexing: summary | attribute\n }\n field text type string {\n indexing: summary | index\n }\n }\n}\n\n\nAdd simpler_keras_model.onnx to the schema. * The model_name is an id that can be used in the ranking function to identify which model to use. * The model_file_path is the current path of the .onnx file. When deploying the application, pyvespa will move the file to the correct location inside the Vespa application package folder. * The inputs maps the name of the inputs contained in the ONNX model to the name of the Vespa source that will be used as input to the model. In this case we will create a function called vespa_input that output a tensor of type float with the expected shape (1, 3). * The outputs maps the output name in the ONNX file to the output name that will be recognized by Vespa.\n\nfrom vespa.package import OnnxModel\n\napp_package.schema.add_model(\n OnnxModel(\n model_name=\"ltr_tensorflow\",\n model_file_path=\"simpler_keras_model.onnx\",\n inputs={\"input\": \"vespa_input\"},\n outputs={\"dense\": \"dense\"},\n )\n)\n\nIt is possible to see the addition of the onnx-model section in the search definition below. Note that the model file is expected to be under the files folder inside the final application package folder, but pyvespa takes care of the model file placement when deploying the application.\n\nprint(app_package.schema.schema_to_text)\n\nschema msmarco {\n document msmarco {\n field id type string {\n indexing: summary | attribute\n }\n field text type string {\n indexing: summary | index\n }\n }\n onnx-model ltr_tensorflow {\n file: files/ltr_tensorflow.onnx\n input input:0: vespa_input\n output dense: dense\n }\n}\n\n\nAdd a rank profile named tensorflow that uses the TensorFlow model to rank documents. * first_phase: We use the Vespa ranking feature onnx to access the ONNX model named ltr_tensorflow and use the output dense. We apply the sum because Vespa requires the relevance score to be a scaler and the output of the ONNX model in this case is a tensor of shape (1,1). * vespa_input function: The ONNX model was trained with the features fieldMatch(text).queryCompleteness, fieldMatch(text).significance and nativeRank(text) and expects and tensor of shape (1,3) containing those features. * summary_features: Summary features allow us to specify Vespa features to be included in the output of a query. In this case, we want to access to the model inputs and output to check if the Vespa model evaluation is the same as if we use the original TensorFlow model.\n\nfrom vespa.package import RankProfile, Function\n\napp_package.schema.add_rank_profile(\n RankProfile(\n name=\"tensorflow\", \n first_phase=\"sum(onnx(ltr_tensorflow).dense)\", \n functions=[\n Function(\n name=\"vespa_input\", \n expression=\"tensor<float>(x[1],y[3]):[[\"\n \"fieldMatch(text).queryCompleteness, \"\n \"fieldMatch(text).significance, \"\n \"nativeRank(text)\"\n \"]]\"\n )\n ],\n summary_features=[\n \"onnx(ltr_tensorflow)\", \n \"fieldMatch(text).queryCompleteness\", \n \"fieldMatch(text).significance\", \n \"nativeRank(text)\"\n ]\n )\n)\n\nThe rank-profile called tensorflow can be seen below:\n\nprint(app_package.schema.schema_to_text)\n\nschema msmarco {\n document msmarco {\n field id type string {\n indexing: summary | attribute\n }\n field text type string {\n indexing: summary | index\n }\n }\n onnx-model ltr_tensorflow {\n file: files/ltr_tensorflow.onnx\n input input:0: vespa_input\n output dense: dense\n }\n rank-profile tensorflow {\n function vespa_input() {\n expression {\n tensor<float>(x[1],y[3]):[[fieldMatch(text).queryCompleteness, fieldMatch(text).significance, nativeRank(text)]]\n }\n }\n first-phase {\n expression {\n sum(onnx(ltr_tensorflow).dense)\n }\n }\n summary-features {\n onnx(ltr_tensorflow)\n fieldMatch(text).queryCompleteness\n fieldMatch(text).significance\n nativeRank(text)\n }\n }\n}\n\n\nNow that we are done with the application package definition. We can deploy the application:\n\nfrom vespa.deployment import VespaDocker\n\nvespa_docker = VespaDocker()\napp = vespa_docker.deploy(application_package=app_package)\n\nWaiting for configuration server, 0/300 seconds...\nWaiting for configuration server, 5/300 seconds...\nWaiting for application status, 0/300 seconds...\nWaiting for application status, 5/300 seconds...\nWaiting for application status, 10/300 seconds...\nWaiting for application status, 15/300 seconds...\nWaiting for application status, 20/300 seconds...\nWaiting for application status, 25/300 seconds...\nFinished deployment." + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#feed-the-application", + "href": "notebooks/tensorflow-via-onnx.html#feed-the-application", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Feed the application", + "text": "Feed the application\nOnce the application is running, it is time to feed msmarco passage data to it.\n\nfrom learntorank.passage import PassageData\n\ndataset = PassageData.load()\n\nWe are going to use only 10 documents because our goal here is to show that Vespa returns the correct predictions from the TensorFlow model.\n\ndata = dataset.get_corpus().head(10)\ndata.rename(columns={'doc_id': 'id'}, inplace=True)\n\n\ndata.head()\n\n\n\n\n\n\n\n\nid\ntext\n\n\n\n\n0\n5954248\nWhy GameStop is excited for Dragon Age: Inquis...\n\n\n1\n7290700\nmetaplasia definition: 1. abnormal change of o...\n\n\n2\n5465518\nCandice Net Worth. According to the report of ...\n\n\n3\n3100518\nUnder the Base Closure Act, March AFB was down...\n\n\n4\n3207764\nThere are a number of career opportunities for...\n\n\n\n\n\n\n\nFeed the data to the application.\n\nresult = app.feed_df(df=data, include_id=True)\n\nSuccessful documents fed: 10/10.\nBatch progress: 1/1." + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#validate-vespa-predictions", + "href": "notebooks/tensorflow-via-onnx.html#validate-vespa-predictions", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Validate Vespa predictions", + "text": "Validate Vespa predictions\nGet query from the small dev set to use to validate Vespa TensorFlow predictions.\n\nquery_text = dataset.get_queries(type=\"dev\").iloc[0,1]\nquery_text = query_text.replace(\"'\", \"\")\n\n\nquery_text\n\n'why say the sky is the limit'\n\n\nThe code below shows the YQL expression that will be used to select the documents to be ranked.\n\n\"select * from sources * where ({{grammar: 'any', defaultIndex: 'text'}}userInput('{}'))\".format(query_text)\n\n\"select * from sources * where ({grammar: 'any', defaultIndex: 'text'}userInput('why say the sky is the limit'))\"\n\n\nThe function get_vespa_prediction_and_features will match documents using the YQL expression above and rank the documents with the rank-profile tensorflow that we defined in the Vespa application package.\n\ndef get_vespa_prediction_and_features(query_text):\n # Send query and extract hits\n hits = app.query(\n body={\n \"yql\": \"select * from sources * where ({{'grammar': 'any', 'defaultIndex': 'text'}}userInput('{}'));\".format(query_text),\n \"ranking\": \"tensorflow\"\n }\n ).hits\n result =[]\n # For each hit, extract the inputs to the model along with model predictions computed by Vespa\n for hit in hits:\n result.append({\n \"fieldMatch(text).queryCompleteness\": hit[\"fields\"][\"summaryfeatures\"][\"fieldMatch(text).queryCompleteness\"],\n \"fieldMatch(text).significance\": hit[\"fields\"][\"summaryfeatures\"][\"fieldMatch(text).significance\"],\n \"nativeRank(text)\": hit[\"fields\"][\"summaryfeatures\"][\"nativeRank(text)\"],\n \"vespa_prediction\": hit[\"relevance\"], \n })\n return pd.DataFrame.from_records(result)\n\nInputs and vespa predictions:\n\npredictions = get_vespa_prediction_and_features(query_text=query_text)\npredictions\n\n\n\n\n\n\n\n\nfieldMatch(text).queryCompleteness\nfieldMatch(text).significance\nnativeRank(text)\nvespa_prediction\n\n\n\n\n0\n0.285714\n0.199799\n0.061853\n0.360788\n\n\n1\n0.571429\n0.415687\n0.086940\n-0.128510\n\n\n2\n0.428571\n0.302071\n0.065154\n-0.240481\n\n\n3\n0.428571\n0.302071\n0.050600\n-0.670632\n\n\n4\n0.428571\n0.302071\n0.049802\n-0.694231\n\n\n5\n0.285714\n0.199799\n0.025552\n-0.712175\n\n\n6\n0.428571\n0.302071\n0.045398\n-0.824390\n\n\n\n\n\n\n\nCompute predictions from the TensorFlow model simpler_model directly:\n\npredictions[\"tf_prediction\"] = predictions[\n [\"fieldMatch(text).queryCompleteness\", \"fieldMatch(text).significance\", \"nativeRank(text)\"]\n].apply(lambda x: simpler_model.predict([x.tolist()])[0][0], axis=1)\n\n1/1 [==============================] - 0s 71ms/step\n1/1 [==============================] - 0s 28ms/step\n1/1 [==============================] - 0s 29ms/step\n1/1 [==============================] - 0s 29ms/step\n1/1 [==============================] - 0s 28ms/step\n1/1 [==============================] - 0s 26ms/step\n1/1 [==============================] - 0s 26ms/step\n\n\n\npredictions\n\n\n\n\n\n\n\n\nfieldMatch(text).queryCompleteness\nfieldMatch(text).significance\nnativeRank(text)\nvespa_prediction\ntf_prediction\n\n\n\n\n0\n0.285714\n0.199799\n0.061853\n0.360788\n0.360788\n\n\n1\n0.571429\n0.415687\n0.086940\n-0.128510\n-0.128510\n\n\n2\n0.428571\n0.302071\n0.065154\n-0.240481\n-0.240481\n\n\n3\n0.428571\n0.302071\n0.050600\n-0.670632\n-0.670632\n\n\n4\n0.428571\n0.302071\n0.049802\n-0.694231\n-0.694231\n\n\n5\n0.285714\n0.199799\n0.025552\n-0.712175\n-0.712176\n\n\n6\n0.428571\n0.302071\n0.045398\n-0.824390\n-0.824390\n\n\n\n\n\n\n\nCheck that the predictions from the model deployed in Vespa are (almost) equal to the predictions obtained directly from the model.\n\nfrom numpy.testing import assert_almost_equal\n\nassert_almost_equal(predictions[\"vespa_prediction\"].tolist(), predictions[\"tf_prediction\"].tolist(), 5)" + }, + { + "objectID": "notebooks/tensorflow-via-onnx.html#clean-environment", + "href": "notebooks/tensorflow-via-onnx.html#clean-environment", + "title": "TensorFlow: Deploy model to Vespa through ONNX", + "section": "Clean environment", + "text": "Clean environment\n\nimport shutil\n\nshutil.rmtree(\"simpler_keras_model\") \nvespa_docker.container.stop(timeout=600)\nvespa_docker.container.remove()" + }, + { + "objectID": "stateless_sequence_classification_task.html", + "href": "stateless_sequence_classification_task.html", + "title": "Sequence Classification task", + "section": "", + "text": "Vespa has implemented accelerated model evaluation using ONNX Runtime in the stateless cluster. This opens up new usage areas for Vespa, such as serving model predictions." + }, + { + "objectID": "stateless_sequence_classification_task.html#define-the-model-server", + "href": "stateless_sequence_classification_task.html#define-the-model-server", + "title": "Sequence Classification task", + "section": "Define the model server", + "text": "Define the model server\nThe SequenceClassification task takes a text input and returns an array of floats that depends on the model used to solve the task. The model argument can be the id of the model as defined by the huggingface model hub.\n\nfrom learntorank.ml import SequenceClassification\n\ntask = SequenceClassification(\n model_id=\"bert_tiny\", \n model=\"google/bert_uncased_L-2_H-128_A-2\"\n)\n\nA ModelServer is a simplified application package focused on stateless model evaluation. It can take as many tasks as we want.\n\nfrom learntorank.ml import ModelServer\n\nmodel_server = ModelServer(\n name=\"bertModelServer\",\n tasks=[task],\n)" + }, + { + "objectID": "stateless_sequence_classification_task.html#deploy-the-model-server", + "href": "stateless_sequence_classification_task.html#deploy-the-model-server", + "title": "Sequence Classification task", + "section": "Deploy the model server", + "text": "Deploy the model server\nWe can either host our model server on Vespa Cloud or deploy it locally using a Docker container.\n\nfrom vespa.deployment import VespaDocker\n\nvespa_docker = VespaDocker()\napp = vespa_docker.deploy(application_package=model_server)\n\nUsing framework PyTorch: 1.12.1\nFound input input_ids with shape: {0: 'batch', 1: 'sequence'}\nFound input token_type_ids with shape: {0: 'batch', 1: 'sequence'}\nFound input attention_mask with shape: {0: 'batch', 1: 'sequence'}\nFound output output_0 with shape: {0: 'batch'}\nEnsuring inputs are in correct order\nposition_ids is not present in the generated input list.\nGenerated inputs order: ['input_ids', 'attention_mask', 'token_type_ids']\nWaiting for configuration server, 0/300 seconds...\nWaiting for configuration server, 5/300 seconds...\nWaiting for application status, 0/300 seconds...\nWaiting for application status, 5/300 seconds...\nFinished deployment." + }, + { + "objectID": "stateless_sequence_classification_task.html#get-model-information", + "href": "stateless_sequence_classification_task.html#get-model-information", + "title": "Sequence Classification task", + "section": "Get model information", + "text": "Get model information\nGet models available:\n\napp.get_model_endpoint()\n\n{'bert_tiny': 'http://localhost:8080/model-evaluation/v1/bert_tiny'}\n\n\nGet information about a specific model:\n\napp.get_model_endpoint(model_id=\"bert_tiny\")\n\n{'model': 'bert_tiny',\n 'functions': [{'function': 'output_0',\n 'info': 'http://localhost:8080/model-evaluation/v1/bert_tiny/output_0',\n 'eval': 'http://localhost:8080/model-evaluation/v1/bert_tiny/output_0/eval',\n 'arguments': [{'name': 'input_ids', 'type': 'tensor(d0[],d1[])'},\n {'name': 'attention_mask', 'type': 'tensor(d0[],d1[])'},\n {'name': 'token_type_ids', 'type': 'tensor(d0[],d1[])'}]}]}" + }, + { + "objectID": "stateless_sequence_classification_task.html#get-predictions", + "href": "stateless_sequence_classification_task.html#get-predictions", + "title": "Sequence Classification task", + "section": "Get predictions", + "text": "Get predictions\nGet a prediction:\n\napp.predict(x=\"this is a test\", model_id=\"bert_tiny\")\n\n[-0.00954509899020195, 0.2504960000514984]" + }, + { + "objectID": "stateless_sequence_classification_task.html#cleanup", + "href": "stateless_sequence_classification_task.html#cleanup", + "title": "Sequence Classification task", + "section": "Cleanup", + "text": "Cleanup\n\nfrom shutil import rmtree\n\nvespa_docker.container.stop(timeout=600)\nvespa_docker.container.remove()" + }, + { + "objectID": "notebooks/query-model.html", + "href": "notebooks/query-model.html", + "title": "Query models", + "section": "", + "text": "from learntorank.query import QueryModel, Ranking, OR\n\nstandard_query_model = QueryModel(\n name=\"or_bm25\",\n match_phase = OR(),\n ranking = Ranking(name=\"bm25\")\n)\nStarting in version 0.5.0 we can bypass the pyvespa high-level API and create a QueryModel with the full flexibility of the Vespa Query API. This is useful for use cases not covered by the pyvespa API and for users that are familiar with and prefer to work with the Vespa Query API.\ndef body_function(query):\n body = {'yql': 'select * from sources * where userQuery();',\n 'query': query,\n 'type': 'any',\n 'ranking': {'profile': 'bm25', 'listFeatures': 'false'}}\n return body\n\nflexible_query_model = QueryModel(body_function = body_function)\nThe flexible_query_model defined above is equivalent to the standard_query_model, as we can see when querying the app. We will use the cord19 app in our demonstration.\nfrom vespa.application import Vespa\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\nfrom learntorank.query import send_query\n\nstandard_result = send_query(\n app=app, \n query=\"this is a test\", \n query_model=standard_query_model\n)\nstandard_result.get_hits().head(3)\nflexible_result = send_query(\n app=app, \n query=\"this is a test\", \n query_model=flexible_query_model\n)\nflexible_result.get_hits().head(3)" + }, + { + "objectID": "notebooks/query-model.html#specify-a-query-model", + "href": "notebooks/query-model.html#specify-a-query-model", + "title": "Query models", + "section": "Specify a query model", + "text": "Specify a query model\n\nQuery + term-matching + rank profile\n\nfrom learntorank.query import QueryModel, OR, Ranking, send_query\n\nresults = send_query(\n app=app,\n query=\"Is remdesivir an effective treatment for COVID-19?\", \n query_model = QueryModel(\n match_phase=OR(), \n ranking=Ranking(name=\"bm25\")\n )\n)\n\n\nresults.number_documents_retrieved\n\n\n\nQuery + term-matching + ann operator + rank_profile\n\nfrom learntorank.query import QueryModel, QueryRankingFeature, ANN, WeakAnd, Union, Ranking\nfrom random import random\n\nmatch_phase = Union(\n WeakAnd(hits = 10), \n ANN(\n doc_vector=\"specter_embedding\", \n query_vector=\"specter_vector\", \n hits = 10,\n label=\"title\"\n )\n)\nranking = Ranking(name=\"related-specter\", list_features=True)\nquery_model = QueryModel(\n query_properties=[QueryRankingFeature(\n name=\"specter_vector\", \n mapping=lambda x: [random() for x in range(768)]\n )],\n match_phase=match_phase, ranking=ranking\n)\n\n\nresults = send_query(\n app=app,\n query=\"Is remdesivir an effective treatment for COVID-19?\", \n query_model=query_model\n)\n\n\nresults.number_documents_retrieved" + }, + { + "objectID": "notebooks/query-model.html#recall-specific-documents", + "href": "notebooks/query-model.html#recall-specific-documents", + "title": "Query models", + "section": "Recall specific documents", + "text": "Recall specific documents\nLet’s take a look at the top 3 ids from the last query.\n\ntop_ids = [hit[\"fields\"][\"id\"] for hit in results.hits[0:3]]\ntop_ids\n\nAssume that we now want to retrieve the second and third ids above. We can do so with the recall argument.\n\nresults_with_recall = send_query(\n app=app,\n query=\"Is remdesivir an effective treatment for COVID-19?\", \n query_model=query_model,\n recall = (\"id\", top_ids[1:3])\n)\n\nIt will only retrieve the documents with Vespa field id that is defined on the list that is inside the tuple.\n\nid_recalled = [hit[\"fields\"][\"id\"] for hit in results_with_recall.hits]\nid_recalled" + }, + { + "objectID": "notebooks/collect-training-data.html", + "href": "notebooks/collect-training-data.html", + "title": "Collect training data from application", + "section": "", + "text": "Connect to the application and define a query model.\n\nfrom vespa.application import Vespa\nfrom learntorank.query import QueryModel, Ranking, OR\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\nquery_model = QueryModel(\n match_phase = OR(),\n ranking = Ranking(name=\"bm25\", list_features=True)\n)\n\nDefine some labeled data.\n\nlabeled_data = [\n {\n \"query_id\": 0, \n \"query\": \"Intrauterine virus infections and congenital heart disease\",\n \"relevant_docs\": [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}]\n },\n {\n \"query_id\": 1, \n \"query\": \"Clinical and immunologic studies in identical twins discordant for systemic lupus erythematosus\",\n \"relevant_docs\": [{\"id\": 1, \"score\": 1}, {\"id\": 5, \"score\": 1}]\n }\n]" + }, + { + "objectID": "notebooks/collect-training-data.html#example-setup", + "href": "notebooks/collect-training-data.html#example-setup", + "title": "Collect training data from application", + "section": "", + "text": "Connect to the application and define a query model.\n\nfrom vespa.application import Vespa\nfrom learntorank.query import QueryModel, Ranking, OR\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\nquery_model = QueryModel(\n match_phase = OR(),\n ranking = Ranking(name=\"bm25\", list_features=True)\n)\n\nDefine some labeled data.\n\nlabeled_data = [\n {\n \"query_id\": 0, \n \"query\": \"Intrauterine virus infections and congenital heart disease\",\n \"relevant_docs\": [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}]\n },\n {\n \"query_id\": 1, \n \"query\": \"Clinical and immunologic studies in identical twins discordant for systemic lupus erythematosus\",\n \"relevant_docs\": [{\"id\": 1, \"score\": 1}, {\"id\": 5, \"score\": 1}]\n }\n]" + }, + { + "objectID": "notebooks/collect-training-data.html#collect-training-data-in-batch", + "href": "notebooks/collect-training-data.html#collect-training-data-in-batch", + "title": "Collect training data from application", + "section": "Collect training data in batch", + "text": "Collect training data in batch\n\nfrom learntorank.query import collect_vespa_features\n\ntraining_data_batch = collect_vespa_features(\n app=app,\n labeled_data = labeled_data,\n id_field = \"id\",\n query_model = query_model,\n number_additional_docs = 2,\n fields=[\"rankfeatures\"]\n)\ntraining_data_batch\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nattributeMatch(authors.first)\nattributeMatch(authors.first).averageWeight\nattributeMatch(authors.first).completeness\nattributeMatch(authors.first).fieldCompleteness\nattributeMatch(authors.first).importance\nattributeMatch(authors.first).matches\nattributeMatch(authors.first).maxWeight\n...\ntextSimilarity(results).fieldCoverage\ntextSimilarity(results).order\ntextSimilarity(results).proximity\ntextSimilarity(results).queryCoverage\ntextSimilarity(results).score\ntextSimilarity(title).fieldCoverage\ntextSimilarity(title).order\ntextSimilarity(title).proximity\ntextSimilarity(title).queryCoverage\ntextSimilarity(title).score\n\n\n\n\n0\n0\n0\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.062500\n0.0\n0.0000\n0.142857\n0.055357\n\n\n1\n3\n0\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.142857\n0.0\n0.4375\n0.142857\n0.224554\n\n\n4\n255164\n0\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n1.000000\n1.0\n1.0000\n1.000000\n1.000000\n\n\n5\n120761\n0\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.300000\n1.0\n1.0000\n0.428571\n0.688571\n\n\n2\n1\n1\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.111111\n0.0\n0.0000\n0.083333\n0.047222\n\n\n3\n5\n1\n1\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.083333\n0.0\n0.0000\n0.083333\n0.041667\n\n\n8\n232555\n1\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n1.000000\n1.0\n1.0000\n1.000000\n1.000000\n\n\n9\n13944\n1\n0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n0.0\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.187500\n1.0\n1.0000\n0.250000\n0.612500\n\n\n\n\n8 rows × 1038 columns" + }, + { + "objectID": "notebooks/cord19/cord19_download_parse_trec_covid.html", + "href": "notebooks/cord19/cord19_download_parse_trec_covid.html", + "title": "How to download and parse TREC-COVID data", + "section": "", + "text": "The files used in this section were originally found at https://ir.nist.gov/covidSubmit/data.html. We will download both the topics and the relevance judgements data. Do not worry about what they are just yet, we will explore them soon.\n\n!curl -fsSLO https://data.vespa.oath.cloud/blog/cord19/topics-rnd5.xml\n!curl -fsSLO https://data.vespa.oath.cloud/blog/cord19/qrels-covid_d5_j0.5-5.txt" + }, + { + "objectID": "notebooks/cord19/cord19_download_parse_trec_covid.html#download-the-data", + "href": "notebooks/cord19/cord19_download_parse_trec_covid.html#download-the-data", + "title": "How to download and parse TREC-COVID data", + "section": "", + "text": "The files used in this section were originally found at https://ir.nist.gov/covidSubmit/data.html. We will download both the topics and the relevance judgements data. Do not worry about what they are just yet, we will explore them soon.\n\n!curl -fsSLO https://data.vespa.oath.cloud/blog/cord19/topics-rnd5.xml\n!curl -fsSLO https://data.vespa.oath.cloud/blog/cord19/qrels-covid_d5_j0.5-5.txt" + }, + { + "objectID": "notebooks/cord19/cord19_download_parse_trec_covid.html#parse-the-data", + "href": "notebooks/cord19/cord19_download_parse_trec_covid.html#parse-the-data", + "title": "How to download and parse TREC-COVID data", + "section": "Parse the data", + "text": "Parse the data\n\nTopics\nThe topics file is in XML format. We can parse it and store in a dictionary called topics. We want to extract a query, a question and a narrative from each topic.\n\nimport xml.etree.ElementTree as ET\n\ntopics = {}\nroot = ET.parse(\"topics-rnd5.xml\").getroot()\nfor topic in root.findall(\"topic\"):\n topic_number = topic.attrib[\"number\"]\n topics[topic_number] = {}\n for query in topic.findall(\"query\"):\n topics[topic_number][\"query\"] = query.text\n for question in topic.findall(\"question\"):\n topics[topic_number][\"question\"] = question.text \n for narrative in topic.findall(\"narrative\"):\n topics[topic_number][\"narrative\"] = narrative.text\n\nThere are a total of 50 topics. For example, we can see the first topic below:\n\ntopics[\"1\"]\n\n{'query': 'coronavirus origin',\n 'question': 'what is the origin of COVID-19',\n 'narrative': \"seeking range of information about the SARS-CoV-2 virus's origin, including its evolution, animal source, and first transmission into humans\"}\n\n\nEach topic has many relevance judgements associated with them.\n\n\nRelevance judgements\nWe can load the relevance judgement data directly into a pandas DataFrame.\n\nimport pandas as pd\n\nrelevance_data = pd.read_csv(\"qrels-covid_d5_j0.5-5.txt\", sep=\" \", header=None)\nrelevance_data.columns = [\"topic_id\", \"round_id\", \"cord_uid\", \"relevancy\"]\n\nThe relevance data contains all the relevance judgements made throughout the 5 rounds of the competition. relevancy equals to 0 is irrelevant, 1 is relevant and 2 is highly relevant.\n\nrelevance_data.head()\n\n\n\n\n\n\n\n\ntopic_id\nround_id\ncord_uid\nrelevancy\n\n\n\n\n0\n1\n4.5\n005b2j4b\n2\n\n\n1\n1\n4.0\n00fmeepz\n1\n\n\n2\n1\n0.5\n010vptx3\n2\n\n\n3\n1\n2.5\n0194oljo\n1\n\n\n4\n1\n4.0\n021q9884\n1\n\n\n\n\n\n\n\nWe are going to remove two rows that have relevancy equal to -1, which I am assuming is an error.\n\nrelevance_data[relevance_data.relevancy == -1]\n\n\n\n\n\n\n\n\ntopic_id\nround_id\ncord_uid\nrelevancy\n\n\n\n\n55873\n38\n5.0\n9hbib8b3\n-1\n\n\n69173\n50\n5.0\nucipq8uk\n-1\n\n\n\n\n\n\n\n\nrelevance_data = relevance_data[relevance_data.relevancy >= 0]\n\nNext we will discuss how we can use this data to evaluate and improve cord19 search app." + }, + { + "objectID": "notebooks/learning-to-rank-ignore.html", + "href": "notebooks/learning-to-rank-ignore.html", + "title": "Learning to rank", + "section": "", + "text": "Vespa logo\nThis notebook is WIP and not runnable - ToDo FIXME" + }, + { + "objectID": "notebooks/learning-to-rank-ignore.html#data", + "href": "notebooks/learning-to-rank-ignore.html#data", + "title": "Learning to rank", + "section": "Data", + "text": "Data\nThis section describes the data that we are going to use to give a brief overview of the pyvespa ranking framework. The data was collected from a running Vespa application indexed with MS MARCO data. For each relevant (document_id, query_id)-pair we collected 9 random matched documents. Relevant documents have label=1 and non-relevant documents have label=0. In addition, many Vespa ranking features computed based on document and query interaction are included.\n\ntrain_df = pd.read_csv(\"https://data.vespa.oath.cloud/blog/ranking/train_sample.csv\")\n\nThe data used here is a sample containing 100.000 rows and 71 features.\n\ntrain_df.shape\n\n(100000, 74)\n\n\n\ntrain_df.head(10)\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nelementCompleteness(body).completeness\nelementCompleteness(body).fieldCompleteness\nelementCompleteness(body).queryCompleteness\nfieldMatch(body)\nfieldMatch(body).absoluteOccurrence\nfieldMatch(body).absoluteProximity\nfieldMatch(body).completeness\n...\nterm(3).significance\nterm(3).weight\nterm(4).connectedness\nterm(4).significance\nterm(4).weight\ntextSimilarity(body).fieldCoverage\ntextSimilarity(body).order\ntextSimilarity(body).proximity\ntextSimilarity(body).queryCoverage\ntextSimilarity(body).score\n\n\n\n\n0\n27061\n3\n0\n0.358796\n0.092593\n0.625\n0.127746\n0.022000\n0.02600\n0.598380\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.092593\n0.250000\n0.437500\n0.625\n0.396644\n\n\n1\n257\n3\n0\n0.359670\n0.094340\n0.625\n0.092319\n0.018000\n0.03500\n0.598467\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.094340\n0.750000\n0.234375\n0.625\n0.400899\n\n\n2\n363\n3\n0\n0.277397\n0.054795\n0.500\n0.141511\n0.030000\n0.07100\n0.477740\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.054795\n0.666667\n0.640625\n0.500\n0.485178\n\n\n3\n22682\n3\n0\n0.333686\n0.042373\n0.625\n0.250817\n0.056000\n0.10000\n0.595869\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.042373\n0.250000\n0.324219\n0.625\n0.346951\n\n\n4\n160\n3\n0\n0.295455\n0.090909\n0.500\n0.118351\n0.015000\n0.05000\n0.479545\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.090909\n0.666667\n0.557292\n0.500\n0.463234\n\n\n5\n228\n3\n0\n0.286364\n0.072727\n0.500\n0.148612\n0.015000\n0.10000\n0.478636\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.072727\n0.000000\n0.286458\n0.500\n0.264806\n\n\n6\n3901893\n3\n0\n0.433824\n0.117647\n0.750\n0.345256\n0.025000\n0.07700\n0.718382\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.117647\n0.600000\n0.575000\n0.750\n0.539779\n\n\n7\n1142680\n3\n1\n0.412037\n0.074074\n0.750\n0.343120\n0.046667\n0.07700\n0.716204\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.074074\n0.600000\n0.615625\n0.750\n0.545284\n\n\n8\n141\n3\n0\n0.286364\n0.072727\n0.500\n0.081461\n0.027500\n0.10000\n0.478636\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.072727\n0.666667\n0.406250\n0.500\n0.406733\n\n\n9\n3060834\n3\n0\n0.410294\n0.070588\n0.750\n0.308250\n0.045000\n0.06675\n0.716029\n...\n0.504935\n100.0\n0.1\n0.674337\n100.0\n0.070588\n0.400000\n0.715625\n0.750\n0.549586\n\n\n\n\n10 rows × 74 columns\n\n\n\nSimilarly, we collected data based on the MS MARCO queries contained on the dev set.\n\ndev_df = pd.read_csv(\"https://data.vespa.oath.cloud/blog/ranking/dev_sample.csv\")\n\n\ndev_df.shape\n\n(74103, 72)\n\n\n\ndev_df.head(10)\n\n\n\n\n\n\n\n\ndocument_id\nquery_id\nlabel\nelementCompleteness(body).completeness\nelementCompleteness(body).fieldCompleteness\nelementCompleteness(body).queryCompleteness\nfieldMatch(body)\nfieldMatch(body).absoluteOccurrence\nfieldMatch(body).absoluteProximity\nfieldMatch(body).completeness\n...\nterm(3).significance\nterm(3).weight\nterm(4).connectedness\nterm(4).significance\nterm(4).weight\ntextSimilarity(body).fieldCoverage\ntextSimilarity(body).order\ntextSimilarity(body).proximity\ntextSimilarity(body).queryCoverage\ntextSimilarity(body).score\n\n\n\n\n0\n8066640\n2\n0\n0.380952\n0.095238\n0.666667\n0.427344\n0.01\n0.1\n0.638095\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.095238\n1.0\n1.0\n0.666667\n0.719048\n\n\n1\n4339068\n2\n1\n0.346667\n0.026667\n0.666667\n0.444933\n0.04\n0.1\n0.634667\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.026667\n1.0\n1.0\n0.666667\n0.705333\n\n\n2\n762768\n2\n0\n0.343750\n0.020833\n0.666667\n0.088859\n0.01\n0.1\n0.634375\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.020833\n1.0\n0.0\n0.666667\n0.354167\n\n\n3\n3370\n2\n0\n0.180180\n0.027027\n0.333333\n0.162049\n0.01\n0.1\n0.318018\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.027027\n0.0\n0.0\n0.333333\n0.105405\n\n\n4\n6060\n2\n0\n0.175287\n0.017241\n0.333333\n0.145722\n0.01\n0.1\n0.317529\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.017241\n0.0\n0.0\n0.333333\n0.103448\n\n\n5\n3798\n2\n0\n0.180556\n0.027778\n0.333333\n0.166942\n0.01\n0.1\n0.318056\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.027778\n0.0\n0.0\n0.333333\n0.105556\n\n\n6\n2731175\n2\n0\n0.345833\n0.025000\n0.666667\n0.398800\n0.01\n0.1\n0.634583\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.025000\n1.0\n1.0\n0.666667\n0.705000\n\n\n7\n3634083\n2\n0\n0.351190\n0.035714\n0.666667\n0.423611\n0.02\n0.1\n0.635119\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.035714\n1.0\n1.0\n0.666667\n0.707143\n\n\n8\n112126\n2\n0\n0.176282\n0.019231\n0.333333\n0.177009\n0.02\n0.1\n0.317628\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.019231\n0.0\n0.0\n0.333333\n0.103846\n\n\n9\n3387\n2\n0\n0.178571\n0.023810\n0.333333\n0.171357\n0.01\n0.1\n0.317857\n...\n0.0\n0.0\n0.0\n0.0\n0.0\n0.023810\n0.0\n0.0\n0.333333\n0.104762\n\n\n\n\n10 rows × 72 columns" + }, + { + "objectID": "notebooks/learning-to-rank-ignore.html#listwise-ranking-framework", + "href": "notebooks/learning-to-rank-ignore.html#listwise-ranking-framework", + "title": "Learning to rank", + "section": "Listwise ranking framework", + "text": "Listwise ranking framework\nThe ListwiseRankingFramework uses TensorFlow Ranking to minimize a listwise loss function that is a smooth approximation of the NDCG metric. The following parameters need to be specified:\n\nfrom learntorank.ranking import ListwiseRankingFramework\n\nranking_framework = ListwiseRankingFramework(\n #\n # Task related \n #\n number_documents_per_query=10, # The size of the list for each sample\n top_n=10, # What NDCG position we want to optmize, e.g. NDCG@10\n #\n # Data pipeline \n #\n batch_size=32, # Batch size used when fitting models to the data\n shuffle_buffer_size=1000, # The buffer size used when shuffling data batches.\n #\n # Hyperparameter tuning \n #\n tuner_max_trials=3, # How many trials to execute when search hyperparameters\n tuner_executions_per_trial=1, # How may model fit per trial\n tuner_epochs=10, # How many epochs to use per execution of the trial\n tuner_early_stop_patience=None, # Set patience number for early stopping\n #\n # Final model\n #\n final_epochs=30 # Number of epochs to use when fitting the model with specific hyperparameters.\n)\n\nWARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\nINFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n\n\n\nData pipeline\nIt is possible to create TensorFlow data pipelines (tf.data.Dataset) either from in-memory data frames or directly from .csv files to avoid the need to load large file into memory. The data pipelines are suited for listwise ranking and can be used as part of a custom tensorflow workflow if desired.\nCreate a tf.data.Dataset from in-memory data frames:\n\ntf_ds = ranking_framework.listwise_tf_dataset_from_df(\n df=train_df, \n feature_names=[\"nativeFieldMatch\", \"nativeProximity\", \"nativeRank\"],\n shuffle_buffer_size=3,\n batch_size=1\n)\n\nNote that the is already suited for listwise learning.\n\nfor batch in tf_ds.take(1):\n print(batch)\n\n(<tf.Tensor: shape=(1, 10, 3), dtype=float32, numpy=\narray([[[1.9765680e-01, 6.5953881e-02, 9.5175676e-02],\n [1.3242842e-01, 1.1140537e-01, 7.1235448e-02],\n [3.4112938e-02, 1.2160993e-37, 1.5161305e-02],\n [1.5705481e-01, 4.0344268e-02, 7.4284837e-02],\n [8.6454414e-02, 3.2825880e-02, 4.2071503e-02],\n [1.9139472e-01, 1.1913208e-01, 9.8301217e-02],\n [4.8045117e-02, 1.2160993e-37, 2.1353386e-02],\n [1.4903504e-01, 1.3032080e-01, 8.0717884e-02],\n [6.3953400e-02, 2.8740479e-02, 3.1617120e-02],\n [1.5656856e-01, 6.8069249e-02, 7.7149279e-02]]], dtype=float32)>, <tf.Tensor: shape=(1, 10), dtype=float32, numpy=array([[0., 0., 0., 0., 0., 0., 0., 1., 0., 0.]], dtype=float32)>)\n\n\nFor large data, we can also create a listwise tf.data.Dataset directly from a .csv file, without the need to load it into memory:\n\ntrain_df.to_csv(\"train_sample.csv\", index=False)\n\n\ntf_ds = ranking_framework.listwise_tf_dataset_from_csv(\n file_path=\"train_sample.csv\",\n feature_names=[\"nativeFieldMatch\", \"nativeProximity\", \"nativeRank\"],\n shuffle_buffer_size=3,\n batch_size=1\n)\n\n\nfor batch in tf_ds.take(1):\n print(batch)\n\n(<tf.Tensor: shape=(1, 10, 3), dtype=float32, numpy=\narray([[[0.08348585, 0.04784278, 0.04242069],\n [0.08451388, 0.01466913, 0.03919163],\n [0.07139124, 0.02419666, 0.03441796],\n [0.07348892, 0.02119719, 0.03501699],\n [0.11205826, 0.10210748, 0.06114895],\n [0.06779736, 0.02308168, 0.03269679],\n [0.08361208, 0.00839302, 0.03809348],\n [0.13477945, 0.13513905, 0.07491743],\n [0.17734438, 0.18263273, 0.09911225],\n [0.12978926, 0.15896696, 0.07534712]]], dtype=float32)>, <tf.Tensor: shape=(1, 10), dtype=float32, numpy=array([[0., 0., 0., 0., 0., 0., 0., 0., 1., 0.]], dtype=float32)>)\n\n\n\n\nPre-defined models\nThe ranking framework comes with same pre-defined models in case you don’t want to use the data pipelines to create your own workflow. It is possible to specify either a DataFrame or a .csv file path as the train and dev input data. If the hyperparameters argument is not specified it will search through the hyperparameter space accordinng to the arguments defined when creating and instance of the ListwiseRankingFramework.\n\nLinear model\n\nweights, dev_eval, best_hyperparams = ranking_framework.fit_linear_model(\n train_data=train_df, \n dev_data=dev_df, \n feature_names=[\n \"fieldMatch(body).proximity\",\n \"fieldMatch(body).queryCompleteness\",\n \"fieldMatch(body).significance\",\n \"nativeFieldMatch\",\n \"nativeProximity\",\n \"nativeRank\",\n ],\n hyperparameters=None # Search for best hyperparameters\n)\n\n\nbest_hyperparams\n\n{'learning_rate': 6.018683626059954}\n\n\n\nweights\n\n{'feature_names': ['fieldMatch(body).proximity',\n 'fieldMatch(body).queryCompleteness',\n 'fieldMatch(body).significance',\n 'nativeFieldMatch',\n 'nativeProximity',\n 'nativeRank'],\n 'linear_model_weights': [0.46931159496307373,\n -30.97307014465332,\n 28.785017013549805,\n 18.257308959960938,\n 12.566983222961426,\n 10.918502807617188]}\n\n\n\ndev_eval\n\n0.7916887402534485\n\n\nIf we instead specify the hyperpameters, hyperparameter search will be skipped.\n\nweights, dev_eval, best_hyperparams = ranking_framework.fit_linear_model(\n train_data=train_df, \n dev_data=dev_df, \n feature_names=[\n \"fieldMatch(body).proximity\",\n \"fieldMatch(body).queryCompleteness\",\n \"fieldMatch(body).significance\",\n \"nativeFieldMatch\",\n \"nativeProximity\",\n \"nativeRank\",\n ],\n hyperparameters={'learning_rate': 6.018683626059954} \n)\n\n\n\nLasso model\n\nweights, dev_eval, best_hyperparams = ranking_framework.fit_lasso_linear_model(\n train_data=train_df, \n dev_data=dev_df, \n feature_names=[\n \"fieldMatch(body).proximity\",\n \"fieldMatch(body).queryCompleteness\",\n \"fieldMatch(body).significance\",\n \"nativeFieldMatch\",\n \"nativeProximity\",\n \"nativeRank\",\n ]\n)\n\n\nprint(best_hyperparams)\n\n{'lambda': 0.0023227311360666802, 'learning_rate': 0.14885653869373894}\n\n\n\nprint(weights)\n\n{'feature_names': ['fieldMatch(body).proximity', 'fieldMatch(body).queryCompleteness', 'fieldMatch(body).significance', 'nativeFieldMatch', 'nativeProximity', 'nativeRank'], 'normalization_mean': [0.8184928894042969, 0.530807375907898, 0.5052036643028259, 0.0906180813908577, 0.039063721895217896, 0.04461509734392166], 'normalization_sd': [0.08662283420562744, 0.05760122463107109, 0.06236378848552704, 0.003072209656238556, 0.003147233510389924, 0.0008713427814655006], 'normalization_number_data': 96990, 'linear_model_weights': [-0.022373167797923088, -2.1850321292877197, 2.055746078491211, 0.21248634159564972, 0.2774745225906372, 0.6118378043174744]}\n\n\n\nprint(dev_eval)\n\n0.7700856328010559\n\n\n\n\n\nFeature selection\nThe are some pre-defined algorithms that can be used for feature selection. The goal is to find a subset of features that are responsible for most of the evaluation metric gains.\n\nLasso model search\nFit a lasso model with all feature_names. Sequentially remove the feature with the smallest absolute weight until there is only one feature in the model.\n\nresults = ranking_framework.lasso_model_search(\n train_data=train_df, \n dev_data=dev_df, \n feature_names=[\n \"fieldMatch(body).proximity\",\n \"fieldMatch(body).queryCompleteness\",\n \"fieldMatch(body).significance\",\n \"nativeFieldMatch\",\n \"nativeProximity\",\n \"nativeRank\",\n ],\n output_file=\"lasso_model_search.json\",\n)\n\n\n[\n f\"Number of features {len(result['weights']['feature_names'])}; Eval metric: {result['evaluation']}\" \n for result in results\n]\n\n['Number of features 6; Eval metric: 0.7820510864257812',\n 'Number of features 5; Eval metric: 0.7812100052833557',\n 'Number of features 4; Eval metric: 0.7958707809448242',\n 'Number of features 3; Eval metric: 0.7378504872322083',\n 'Number of features 2; Eval metric: 0.7098456025123596',\n 'Number of features 1; Eval metric: 0.7048170566558838']\n\n\n\n[result['weights']['feature_names'] for result in results]\n\n[['fieldMatch(body).proximity',\n 'fieldMatch(body).queryCompleteness',\n 'fieldMatch(body).significance',\n 'nativeFieldMatch',\n 'nativeProximity',\n 'nativeRank'],\n ['fieldMatch(body).queryCompleteness',\n 'fieldMatch(body).significance',\n 'nativeFieldMatch',\n 'nativeProximity',\n 'nativeRank'],\n ['fieldMatch(body).queryCompleteness',\n 'fieldMatch(body).significance',\n 'nativeFieldMatch',\n 'nativeRank'],\n ['fieldMatch(body).queryCompleteness',\n 'fieldMatch(body).significance',\n 'nativeRank'],\n ['fieldMatch(body).queryCompleteness', 'nativeRank'],\n ['nativeRank']]\n\n\n\n\nForward selection\nIncrementally add one feature at a time and keep the features that maximize the validation metric.\n\nforward_results = ranking_framework.forward_selection_model_search(\n train_data=train_df, \n dev_data=dev_df, \n feature_names=[\n \"fieldMatch(body).proximity\",\n \"fieldMatch(body).queryCompleteness\",\n \"fieldMatch(body).significance\",\n \"nativeFieldMatch\",\n \"nativeProximity\",\n \"nativeRank\",\n ],\n output_file=\"forward_model_search.json\",\n)\n\nEvaluation metric for one feature model.\n\n[\n (result[\"evaluation\"], result[\"weights\"][\"feature_names\"]) for \n result in forward_results \n if result[\"number_features\"] == 1\n]\n\n[(0.4771268367767334, ['fieldMatch(body).proximity']),\n (0.5774978995323181, ['fieldMatch(body).queryCompleteness']),\n (0.3523213565349579, ['fieldMatch(body).significance']),\n (0.693596601486206, ['nativeFieldMatch']),\n (0.673930287361145, ['nativeProximity']),\n (0.704784631729126, ['nativeRank'])]\n\n\nEvaluation metric for two features keeping the best feature of the oe-feature model\n\n[\n (result[\"evaluation\"], result[\"weights\"][\"feature_names\"]) for \n result in forward_results \n if result[\"number_features\"] == 2\n]\n\n[(0.7052107453346252, ['nativeRank', 'fieldMatch(body).proximity']),\n (0.7083131670951843, ['nativeRank', 'fieldMatch(body).queryCompleteness']),\n (0.7050297260284424, ['nativeRank', 'fieldMatch(body).significance']),\n (0.7048313617706299, ['nativeRank', 'nativeFieldMatch']),\n (0.7088075876235962, ['nativeRank', 'nativeProximity'])]\n\n\nAnd so on:\n\n[\n (result[\"evaluation\"], result[\"weights\"][\"feature_names\"]) for \n result in forward_results \n if result[\"number_features\"] == 3\n]\n\n[(0.7087035179138184,\n ['nativeRank', 'nativeProximity', 'fieldMatch(body).proximity']),\n (0.7237873673439026,\n ['nativeRank', 'nativeProximity', 'fieldMatch(body).queryCompleteness']),\n (0.7073785662651062,\n ['nativeRank', 'nativeProximity', 'fieldMatch(body).significance']),\n (0.709153413772583, ['nativeRank', 'nativeProximity', 'nativeFieldMatch'])]" + }, + { + "objectID": "notebooks/evaluation.html", + "href": "notebooks/evaluation.html", + "title": "Evaluate application", + "section": "", + "text": "Connect to the application and define a query model.\n\nfrom vespa.application import Vespa\nfrom learntorank.query import QueryModel, Ranking, OR\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\nquery_model = QueryModel(\n match_phase = OR(),\n ranking = Ranking(name=\"bm25\", list_features=True))" + }, + { + "objectID": "notebooks/evaluation.html#example-setup", + "href": "notebooks/evaluation.html#example-setup", + "title": "Evaluate application", + "section": "", + "text": "Connect to the application and define a query model.\n\nfrom vespa.application import Vespa\nfrom learntorank.query import QueryModel, Ranking, OR\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\nquery_model = QueryModel(\n match_phase = OR(),\n ranking = Ranking(name=\"bm25\", list_features=True))" + }, + { + "objectID": "notebooks/evaluation.html#labeled-data", + "href": "notebooks/evaluation.html#labeled-data", + "title": "Evaluate application", + "section": "Labeled data", + "text": "Labeled data\nDefine some labeled data. pyvespa expects labeled data to follow the format illustrated below. It is a list of dict where each dict represents a query containing query_id, query and a list of relevant_docs. Each relevant document contain a required id key and an optional score key.\n\nlabeled_data = [\n {\n \"query_id\": 0, \n \"query\": \"Intrauterine virus infections and congenital heart disease\",\n \"relevant_docs\": [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}]\n },\n {\n \"query_id\": 1, \n \"query\": \"Clinical and immunologic studies in identical twins discordant for systemic lupus erythematosus\",\n \"relevant_docs\": [{\"id\": 1, \"score\": 1}, {\"id\": 5, \"score\": 1}]\n }\n]" + }, + { + "objectID": "notebooks/evaluation.html#define-metrics", + "href": "notebooks/evaluation.html#define-metrics", + "title": "Evaluate application", + "section": "Define metrics", + "text": "Define metrics\n\nfrom learntorank.evaluation import MatchRatio, Recall, ReciprocalRank\n\neval_metrics = [MatchRatio(), Recall(at=10), ReciprocalRank(at=10)]" + }, + { + "objectID": "notebooks/evaluation.html#evaluate-in-batch", + "href": "notebooks/evaluation.html#evaluate-in-batch", + "title": "Evaluate application", + "section": "Evaluate in batch", + "text": "Evaluate in batch\n\nfrom learntorank.evaluation import evaluate\n\nevaluation = evaluate(\n app=app,\n labeled_data = labeled_data,\n eval_metrics = eval_metrics, \n query_model = query_model, \n id_field = \"id\",\n)\nevaluation\n\n\n\n\n\n\n\n\nmodel\ndefault_name\n\n\n\n\nmatch_ratio\nmean\n0.853456\n\n\nmedian\n0.853456\n\n\nstd\n0.055199\n\n\nrecall_10\nmean\n0.000000\n\n\nmedian\n0.000000\n\n\nstd\n0.000000\n\n\nreciprocal_rank_10\nmean\n0.000000\n\n\nmedian\n0.000000\n\n\nstd\n0.000000" + }, + { + "objectID": "notebooks/evaluation.html#evaluate-specific-query", + "href": "notebooks/evaluation.html#evaluate-specific-query", + "title": "Evaluate application", + "section": "Evaluate specific query", + "text": "Evaluate specific query\n\nYou can have finer control with the evaluate_query method.\n\n\nfrom pandas import concat, DataFrame\nfrom learntorank.evaluation import evaluate_query\n\nevaluation = []\nfor query_data in labeled_data:\n query_evaluation = evaluate_query(\n app=app,\n eval_metrics = eval_metrics, \n query_model = query_model, \n query_id = query_data[\"query_id\"], \n query = query_data[\"query\"], \n id_field = \"id\",\n relevant_docs = query_data[\"relevant_docs\"],\n default_score = 0\n )\n evaluation.append(query_evaluation)\nevaluation = DataFrame.from_records(evaluation)\nevaluation\n\n\n\n\n\n\n\n\nmodel\nquery_id\nmatch_ratio\nrecall_10\nreciprocal_rank_10\n\n\n\n\n0\ndefault_name\n0\n0.814425\n0.0\n0\n\n\n1\ndefault_name\n1\n0.892487\n0.0\n0" + }, + { + "objectID": "notebooks/evaluation.html#evaluate-query-under-specific-document-ids", + "href": "notebooks/evaluation.html#evaluate-query-under-specific-document-ids", + "title": "Evaluate application", + "section": "Evaluate query under specific document ids", + "text": "Evaluate query under specific document ids\n\nUse recall to specify which documents should be inlcuded in the evaluation\n\nIn the example below, we include documents with id equal to 0, 1 and 2. Since the relevant documents for this query are the documents with id 0 and 3, we should get recall equal to 0.5.\n\nquery_evaluation = evaluate_query(\n app=app,\n eval_metrics = eval_metrics, \n query_model = query_model, \n query_id = 0, \n query = \"Intrauterine virus infections and congenital heart disease\", \n id_field = \"id\",\n relevant_docs = [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}],\n default_score = 0,\n recall = (\"id\", [0, 1, 2])\n)\nquery_evaluation\n\n{'model': 'default_name',\n 'query_id': 0,\n 'match_ratio': 9.70242657688688e-06,\n 'recall_10': 0.5,\n 'reciprocal_rank_10': 1.0}\n\n\nWe now include documents with id equal to 0, 1, 2 and 3. This should give a recall equal to 1.\n\nquery_evaluation = evaluate_query(\n app=app,\n eval_metrics = eval_metrics, \n query_model = query_model, \n query_id = 0, \n query = \"Intrauterine virus infections and congenital heart disease\", \n id_field = \"id\",\n relevant_docs = [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}],\n default_score = 0,\n recall = (\"id\", [0, 1, 2, 3])\n)\nquery_evaluation\n\n{'model': 'default_name',\n 'query_id': 0,\n 'match_ratio': 1.2936568769182506e-05,\n 'recall_10': 1.0,\n 'reciprocal_rank_10': 1.0}" + }, + { + "objectID": "module_evaluation.html", + "href": "module_evaluation.html", + "title": "evaluation", + "section": "", + "text": "Abstract and concrete classes related to evaluation metrics.\n\nsource\n\n\n\n EvalMetric ()\n\nAbstract class for evaluation metric.\n\nsource\n\n\n\n\n EvalMetric.evaluate_query (query_results, relevant_docs, id_field,\n default_score, detailed_metrics=False)\n\nAbstract method to be implemented by metrics inheriting from EvalMetric to evaluate query results.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\n\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\n\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\n\n\nThe Vespa field representing the document id.\n\n\ndefault_score\n\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nMetric values.\n\n\n\n\nsource\n\n\n\n\n MatchRatio ()\n\nComputes the ratio of documents retrieved by the match phase.\nInstantiate the metric:\n\nmetric = MatchRatio()\n\n\nsource\n\n\n\n\n MatchRatio.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to match ratio metric.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the match ratio. In addition, if detailed_metrics=False, returns the number of retrieved docs _retrieved_docs and the number of docs available in the corpus _docs_available.\n\n\n\nCompute match ratio:\n\nevaluation = metric.evaluate_query(\n query_results=query_results, \n relevant_docs=None,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'match_ratio': 0.01731996353691887}\n\n\nReturn detailed metrics, in addition to match ratio:\n\nevaluation = metric.evaluate_query(\n query_results=query_results,\n relevant_docs=None,\n id_field=\"vespa_id_field\",\n default_score=0,\n detailed_metrics=True,\n)\nevaluation\n\n{'match_ratio': 0.01731996353691887,\n 'match_ratio_retrieved_docs': 1083,\n 'match_ratio_docs_available': 62529}\n\n\n\nsource\n\n\n\n\n TimeQuery ()\n\nCompute the time it takes for Vespa to execute the query..\nInstantiate the metric:\n\ntime_metric = TimeQuery()\n\n\nsource\n\n\n\n\n TimeQuery.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to query time metric.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the match ratio. In addition, if detailed_metrics=False, returns the number of retrieved docs _retrieved_docs and the number of docs available in the corpus _docs_available.\n\n\n\nCompute the query time a client would observe (except network latency).\n\ntime_metric.evaluate_query(\n query_results=query_results, \n relevant_docs=None, \n id_field=\"vespa_id_field\",\n default_score=0\n)\n\n{'search_time': 0.013}\n\n\nInclude detailed metrics. In addition to the search_time above, it returns the time to execute the first protocol phase/matching phase (search_time_query_time) and the time to execute the summary fill protocol phase for the globally ordered top-k hits (search_time_summary_fetch_time).\n\ntime_metric.evaluate_query(\n query_results=query_results, \n relevant_docs=None, \n id_field=\"vespa_id_field\",\n default_score=0,\n detailed_metrics=True\n)\n\n{'search_time': 0.013,\n 'search_time_query_time': 0.01,\n 'search_time_summary_fetch_time': 0.002}\n\n\n\nsource\n\n\n\n\n Recall (at:int)\n\nCompute the recall at position at.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nat\nint\nMaximum position on the resulting list to look for relevant docs.\n\n\nReturns\nNone\n\n\n\n\nInstantiate the metric:\n\nrecall_1 = Recall(at=1)\nrecall_2 = Recall(at=2)\nrecall_3 = Recall(at=3)\n\n\nsource\n\n\n\n\n Recall.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to recall metric.\nThere is an assumption that only documents with score > 0 are relevant. Recall is equal to zero in case no relevant documents with score > 0 is provided.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the recall value.\n\n\n\nCompute recall:\n\nevaluation = recall_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'recall_2': 0.5}\n\n\nCompute recall:\n\nsource\n\n\n\n\n ReciprocalRank (at:int)\n\nCompute the reciprocal rank at position at\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nat\nint\nMaximum position on the resulting list to look for relevant docs.\n\n\n\nInstantiate the metric:\n\nrr_1 = ReciprocalRank(at=1)\nrr_2 = ReciprocalRank(at=2)\nrr_3 = ReciprocalRank(at=3)\n\n\nsource\n\n\n\n\n ReciprocalRank.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to reciprocal rank metric.\nThere is an assumption that only documents with score > 0 are relevant.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the reciprocal rank value.\n\n\n\nCompute reciprocal rank:\n\nevaluation = rr_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'reciprocal_rank_2': 0.5}\n\n\n\nsource\n\n\n\n\n NormalizedDiscountedCumulativeGain (at:int)\n\nCompute the normalized discounted cumulative gain at position at.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nat\nint\nMaximum position on the resulting list to look for relevant docs.\n\n\n\nInstantiate the metric:\n\nndcg_1 = NormalizedDiscountedCumulativeGain(at=1)\nndcg_2 = NormalizedDiscountedCumulativeGain(at=2)\nndcg_3 = NormalizedDiscountedCumulativeGain(at=3)\n\n\nsource\n\n\n\n\n NormalizedDiscountedCumulativeGain.evaluate_query\n (query_results:vespa.i\n o.VespaQueryResponse, \n relevant_docs:List[Dic\n t], id_field:str,\n default_score:int, det\n ailed_metrics=False)\n\nEvaluate query results according to normalized discounted cumulative gain.\nThere is an assumption that documents returned by the query that are not included in the set of relevant documents have score equal to zero. Similarly, if the query returns a number N < at documents, we will assume that those N - at missing scores are equal to zero.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the normalized discounted cumulative gain. In addition, if detailed_metrics=False, returns the ideal discounted cumulative gain _ideal_dcg, the discounted cumulative gain _dcg.\n\n\n\nCompute NDCG:\n\nmetric = NormalizedDiscountedCumulativeGain(at=2)\nevaluation = ndcg_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'ndcg_2': 0.38685280723454163}\n\n\nReturn detailed metrics, in addition to NDCG:\n\nevaluation = ndcg_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n detailed_metrics=True,\n)\nevaluation\n\n{'ndcg_2': 0.38685280723454163,\n 'ndcg_2_ideal_dcg': 1.6309297535714575,\n 'ndcg_2_dcg': 0.6309297535714575}" + }, + { + "objectID": "module_evaluation.html#metrics", + "href": "module_evaluation.html#metrics", + "title": "evaluation", + "section": "", + "text": "Abstract and concrete classes related to evaluation metrics.\n\nsource\n\n\n\n EvalMetric ()\n\nAbstract class for evaluation metric.\n\nsource\n\n\n\n\n EvalMetric.evaluate_query (query_results, relevant_docs, id_field,\n default_score, detailed_metrics=False)\n\nAbstract method to be implemented by metrics inheriting from EvalMetric to evaluate query results.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\n\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\n\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\n\n\nThe Vespa field representing the document id.\n\n\ndefault_score\n\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nMetric values.\n\n\n\n\nsource\n\n\n\n\n MatchRatio ()\n\nComputes the ratio of documents retrieved by the match phase.\nInstantiate the metric:\n\nmetric = MatchRatio()\n\n\nsource\n\n\n\n\n MatchRatio.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to match ratio metric.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the match ratio. In addition, if detailed_metrics=False, returns the number of retrieved docs _retrieved_docs and the number of docs available in the corpus _docs_available.\n\n\n\nCompute match ratio:\n\nevaluation = metric.evaluate_query(\n query_results=query_results, \n relevant_docs=None,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'match_ratio': 0.01731996353691887}\n\n\nReturn detailed metrics, in addition to match ratio:\n\nevaluation = metric.evaluate_query(\n query_results=query_results,\n relevant_docs=None,\n id_field=\"vespa_id_field\",\n default_score=0,\n detailed_metrics=True,\n)\nevaluation\n\n{'match_ratio': 0.01731996353691887,\n 'match_ratio_retrieved_docs': 1083,\n 'match_ratio_docs_available': 62529}\n\n\n\nsource\n\n\n\n\n TimeQuery ()\n\nCompute the time it takes for Vespa to execute the query..\nInstantiate the metric:\n\ntime_metric = TimeQuery()\n\n\nsource\n\n\n\n\n TimeQuery.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to query time metric.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the match ratio. In addition, if detailed_metrics=False, returns the number of retrieved docs _retrieved_docs and the number of docs available in the corpus _docs_available.\n\n\n\nCompute the query time a client would observe (except network latency).\n\ntime_metric.evaluate_query(\n query_results=query_results, \n relevant_docs=None, \n id_field=\"vespa_id_field\",\n default_score=0\n)\n\n{'search_time': 0.013}\n\n\nInclude detailed metrics. In addition to the search_time above, it returns the time to execute the first protocol phase/matching phase (search_time_query_time) and the time to execute the summary fill protocol phase for the globally ordered top-k hits (search_time_summary_fetch_time).\n\ntime_metric.evaluate_query(\n query_results=query_results, \n relevant_docs=None, \n id_field=\"vespa_id_field\",\n default_score=0,\n detailed_metrics=True\n)\n\n{'search_time': 0.013,\n 'search_time_query_time': 0.01,\n 'search_time_summary_fetch_time': 0.002}\n\n\n\nsource\n\n\n\n\n Recall (at:int)\n\nCompute the recall at position at.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nat\nint\nMaximum position on the resulting list to look for relevant docs.\n\n\nReturns\nNone\n\n\n\n\nInstantiate the metric:\n\nrecall_1 = Recall(at=1)\nrecall_2 = Recall(at=2)\nrecall_3 = Recall(at=3)\n\n\nsource\n\n\n\n\n Recall.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to recall metric.\nThere is an assumption that only documents with score > 0 are relevant. Recall is equal to zero in case no relevant documents with score > 0 is provided.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the recall value.\n\n\n\nCompute recall:\n\nevaluation = recall_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'recall_2': 0.5}\n\n\nCompute recall:\n\nsource\n\n\n\n\n ReciprocalRank (at:int)\n\nCompute the reciprocal rank at position at\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nat\nint\nMaximum position on the resulting list to look for relevant docs.\n\n\n\nInstantiate the metric:\n\nrr_1 = ReciprocalRank(at=1)\nrr_2 = ReciprocalRank(at=2)\nrr_3 = ReciprocalRank(at=3)\n\n\nsource\n\n\n\n\n ReciprocalRank.evaluate_query (query_results:vespa.io.VespaQueryResponse,\n relevant_docs:List[Dict], id_field:str,\n default_score:int, detailed_metrics=False)\n\nEvaluate query results according to reciprocal rank metric.\nThere is an assumption that only documents with score > 0 are relevant.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the reciprocal rank value.\n\n\n\nCompute reciprocal rank:\n\nevaluation = rr_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'reciprocal_rank_2': 0.5}\n\n\n\nsource\n\n\n\n\n NormalizedDiscountedCumulativeGain (at:int)\n\nCompute the normalized discounted cumulative gain at position at.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nat\nint\nMaximum position on the resulting list to look for relevant docs.\n\n\n\nInstantiate the metric:\n\nndcg_1 = NormalizedDiscountedCumulativeGain(at=1)\nndcg_2 = NormalizedDiscountedCumulativeGain(at=2)\nndcg_3 = NormalizedDiscountedCumulativeGain(at=3)\n\n\nsource\n\n\n\n\n NormalizedDiscountedCumulativeGain.evaluate_query\n (query_results:vespa.i\n o.VespaQueryResponse, \n relevant_docs:List[Dic\n t], id_field:str,\n default_score:int, det\n ailed_metrics=False)\n\nEvaluate query results according to normalized discounted cumulative gain.\nThere is an assumption that documents returned by the query that are not included in the set of relevant documents have score equal to zero. Similarly, if the query returns a number N < at documents, we will assume that those N - at missing scores are equal to zero.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nquery_results\nVespaQueryResponse\n\nRaw query results returned by Vespa.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n\nScore to assign to the additional documents that are not relevant. Default to 0.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nReturns\ntyping.Dict\n\nReturns the normalized discounted cumulative gain. In addition, if detailed_metrics=False, returns the ideal discounted cumulative gain _ideal_dcg, the discounted cumulative gain _dcg.\n\n\n\nCompute NDCG:\n\nmetric = NormalizedDiscountedCumulativeGain(at=2)\nevaluation = ndcg_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n)\nevaluation\n\n{'ndcg_2': 0.38685280723454163}\n\n\nReturn detailed metrics, in addition to NDCG:\n\nevaluation = ndcg_2.evaluate_query(\n query_results=query_results,\n relevant_docs=relevant_docs,\n id_field=\"vespa_id_field\",\n default_score=0,\n detailed_metrics=True,\n)\nevaluation\n\n{'ndcg_2': 0.38685280723454163,\n 'ndcg_2_ideal_dcg': 1.6309297535714575,\n 'ndcg_2_dcg': 0.6309297535714575}" + }, + { + "objectID": "module_evaluation.html#evaluation-queries-in-batch", + "href": "module_evaluation.html#evaluation-queries-in-batch", + "title": "evaluation", + "section": "Evaluation queries in batch", + "text": "Evaluation queries in batch\n\nsource\n\nevaluate\n\n evaluate (app:vespa.application.Vespa,\n labeled_data:Union[List[Dict],pandas.core.frame.DataFrame],\n eval_metrics:List[__main__.EvalMetric], query_model:Union[learn\n torank.query.QueryModel,List[learntorank.query.QueryModel]],\n id_field:str, default_score:int=0, detailed_metrics=False,\n per_query=False, aggregators=None, timeout=1000, **kwargs)\n\nEvaluate a QueryModel according to a list of EvalMetric.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp\nVespa\n\nConnection to a Vespa application.\n\n\nlabeled_data\ntyping.Union[typing.List[typing.Dict], pandas.core.frame.DataFrame]\n\nData containing query, query_id and relevant docs. See examples below for format.\n\n\neval_metrics\ntyping.List[main.EvalMetric]\n\nEvaluation metrics\n\n\nquery_model\ntyping.Union[learntorank.query.QueryModel, typing.List[learntorank.query.QueryModel]]\n\nQuery models to be evaluated\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\ndefault_score\nint\n0\nScore to assign to the additional documents that are not relevant.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nper_query\nbool\nFalse\nSet to True to return evaluation metrics per query.\n\n\naggregators\nNoneType\nNone\nUsed only if per_query=False. List of pandas friendly aggregators to summarize per model metrics. We use [“mean”, “median”, “std”] by default.\n\n\ntimeout\nint\n1000\nVespa query timeout in ms.\n\n\nkwargs\n\n\n\n\n\nReturns\nDataFrame\n\nReturns query_id and metrics according to the selected evaluation metrics.\n\n\n\nUsage:\nSetup and feed a Vespa application:\n\nfrom learntorank.passage import create_basic_search_package\nfrom learntorank.passage import PassageData\nfrom vespa.deployment import VespaDocker\n\n\napp_package = create_basic_search_package(name=\"EvaluationApp\")\nvespa_docker = VespaDocker(port=8082, cfgsrv_port=19072)\napp = vespa_docker.deploy(application_package=app_package)\ndata = PassageData.load()\nresponses = app.feed_df(\n df=data.get_corpus(), \n include_id=True, \n id_field=\"doc_id\"\n)\n\nDefine query models to be evaluated:\n\nfrom learntorank.query import OR, Ranking\n\n\nbm25_query_model = QueryModel(\n name=\"bm25\", \n match_phase=OR(), \n ranking=Ranking(name=\"bm25\")\n)\nnative_query_model = QueryModel(\n name=\"native_rank\", \n match_phase=OR(), \n ranking=Ranking(name=\"native_rank\")\n)\n\nDefine metrics to compute during evaluation:\n\nmetrics = [\n Recall(at=10), \n ReciprocalRank(at=3), \n NormalizedDiscountedCumulativeGain(at=3)\n]\n\nGet labeled data:\n\nlabeled_data = data.get_labels(type=\"dev\")\nlabeled_data[0:2]\n\n[{'query_id': '1101971',\n 'query': 'why say the sky is the limit',\n 'relevant_docs': [{'id': '7407715', 'score': 1}]},\n {'query_id': '712898',\n 'query': 'what is an cvc in radiology',\n 'relevant_docs': [{'id': '7661336', 'score': 1}]}]\n\n\nEvaluate:\n\nevaluation = evaluate(\n app=app,\n labeled_data=labeled_data, \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n)\nevaluation\n\n\n\n\n\n\n\n\nmodel\nbm25\nnative_rank\n\n\n\n\nrecall_10\nmean\n0.935833\n0.845833\n\n\nmedian\n1.000000\n1.000000\n\n\nstd\n0.215444\n0.342749\n\n\nreciprocal_rank_3\nmean\n0.935000\n0.746667\n\n\nmedian\n1.000000\n1.000000\n\n\nstd\n0.231977\n0.399551\n\n\nndcg_3\nmean\n0.912839\n0.740814\n\n\nmedian\n1.000000\n1.000000\n\n\nstd\n0.242272\n0.387611\n\n\n\n\n\n\n\nThe evaluate function also accepts labeled data as a data frame:\n\nlabeled_df.head()\n\n\n\n\n\n\n\n\nqid\nquery\ndoc_id\nrelevance\n\n\n\n\n0\n1101971\nwhy say the sky is the limit\n7407715\n1\n\n\n1\n712898\nwhat is an cvc in radiology\n7661336\n1\n\n\n2\n154469\ndmv california how long does it take to get id\n7914544\n1\n\n\n3\n930015\nwhat's an epigraph\n7928705\n1\n\n\n4\n860085\nwhat is va tax\n2915383\n1\n\n\n\n\n\n\n\n\nevaluation_df = evaluate(\n app=app,\n labeled_data=labeled_df, \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n)\nevaluation_df\n\n\n\n\n\n\n\n\nmodel\nbm25\nnative_rank\n\n\n\n\nrecall_10\nmean\n0.935833\n0.845833\n\n\nmedian\n1.000000\n1.000000\n\n\nstd\n0.215444\n0.342749\n\n\nreciprocal_rank_3\nmean\n0.935000\n0.746667\n\n\nmedian\n1.000000\n1.000000\n\n\nstd\n0.231977\n0.399551\n\n\nndcg_3\nmean\n0.912839\n0.740814\n\n\nmedian\n1.000000\n1.000000\n\n\nstd\n0.242272\n0.387611\n\n\n\n\n\n\n\nControl which aggregators are computed:\n\nevaluation = evaluate(\n app=app,\n labeled_data=labeled_data, \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n aggregators=[\"mean\", \"std\"]\n)\nevaluation\n\n\n\n\n\n\n\n\nmodel\nbm25\nnative_rank\n\n\n\n\nrecall_10\nmean\n0.935833\n0.845833\n\n\nstd\n0.215444\n0.342749\n\n\nreciprocal_rank_3\nmean\n0.935000\n0.746667\n\n\nstd\n0.231977\n0.399551\n\n\nndcg_3\nmean\n0.912839\n0.740814\n\n\nstd\n0.242272\n0.387611\n\n\n\n\n\n\n\nInclude detailed metrics when available, this includes intermediate steps that are available for some of the metrics:\n\nevaluation = evaluate(\n app=app,\n labeled_data=labeled_data, \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n aggregators=[\"mean\", \"std\"],\n detailed_metrics=True\n)\nevaluation\n\n\n\n\n\n\n\n\nmodel\nbm25\nnative_rank\n\n\n\n\nrecall_10\nmean\n0.935833\n0.845833\n\n\nstd\n0.215444\n0.342749\n\n\nreciprocal_rank_3\nmean\n0.935000\n0.746667\n\n\nstd\n0.231977\n0.399551\n\n\nndcg_3\nmean\n0.912839\n0.740814\n\n\nstd\n0.242272\n0.387611\n\n\nndcg_3_ideal_dcg\nmean\n1.054165\n1.054165\n\n\nstd\n0.207315\n0.207315\n\n\nndcg_3_dcg\nmean\n0.938928\n0.765474\n\n\nstd\n0.225533\n0.387161\n\n\n\n\n\n\n\nGenerate results per query:\n\nevaluation = evaluate(\n app=app,\n labeled_data=labeled_data, \n eval_metrics=metrics, \n query_model=[native_query_model, bm25_query_model], \n id_field=\"doc_id\",\n per_query=True\n)\nevaluation.head()\n\n\n\n\n\n\n\n\nmodel\nquery_id\nrecall_10\nreciprocal_rank_3\nndcg_3\n\n\n\n\n0\nnative_rank\n1101971\n1.0\n1.0\n1.0\n\n\n1\nnative_rank\n712898\n0.0\n0.0\n0.0\n\n\n2\nnative_rank\n154469\n1.0\n0.0\n0.0\n\n\n3\nnative_rank\n930015\n1.0\n0.0\n0.0\n\n\n4\nnative_rank\n860085\n0.0\n0.0\n0.0" + }, + { + "objectID": "module_evaluation.html#evaluate-specific-query", + "href": "module_evaluation.html#evaluate-specific-query", + "title": "evaluation", + "section": "Evaluate specific query", + "text": "Evaluate specific query\n\nsource\n\nevaluate_query\n\n evaluate_query (app:vespa.application.Vespa,\n eval_metrics:List[__main__.EvalMetric],\n query_model:learntorank.query.QueryModel, query_id:str,\n query:str, id_field:str, relevant_docs:List[Dict],\n default_score:int=0, detailed_metrics=False, **kwargs)\n\nEvaluate a single query according to evaluation metrics\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\napp\nVespa\n\nConnection to a Vespa application.\n\n\neval_metrics\ntyping.List[main.EvalMetric]\n\nEvaluation metrics\n\n\nquery_model\nQueryModel\n\nQuery model to be evaluated\n\n\nquery_id\nstr\n\nQuery id represented as str.\n\n\nquery\nstr\n\nQuery string.\n\n\nid_field\nstr\n\nThe Vespa field representing the document id.\n\n\nrelevant_docs\ntyping.List[typing.Dict]\n\nEach dict contains a doc id a optionally a doc score.\n\n\ndefault_score\nint\n0\nScore to assign to the additional documents that are not relevant.\n\n\ndetailed_metrics\nbool\nFalse\nReturn intermediate computations if available.\n\n\nkwargs\n\n\n\n\n\nReturns\ntyping.Dict\n\nContains query_id and metrics according to the selected evaluation metrics.\n\n\n\nUsage:\n\napp = Vespa(url = \"https://api.cord19.vespa.ai\")\nquery_model = QueryModel(\n match_phase = OR(),\n ranking = Ranking(name=\"bm25\", list_features=True))\n\nEvaluate a single query:\n\nquery_evaluation = evaluate_query(\n app=app,\n eval_metrics = eval_metrics, \n query_model = bm25_query_model, \n query_id = \"0\", \n query = \"Intrauterine virus infections and congenital heart disease\", \n id_field = \"id\",\n relevant_docs = [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}],\n default_score = 0\n)\nquery_evaluation\n\n{'model': 'bm25',\n 'query_id': '0',\n 'match_ratio': 0.814424921006077,\n 'recall_10': 0.0,\n 'reciprocal_rank_10': 0}" + }, + { + "objectID": "module_evaluation.html#evaluate-query-under-specific-document-ids", + "href": "module_evaluation.html#evaluate-query-under-specific-document-ids", + "title": "evaluation", + "section": "Evaluate query under specific document ids", + "text": "Evaluate query under specific document ids\nUse recall to specify which documents should be included in the evaluation.\nIn the example below, we include documents with id equal to 0, 1 and 2. Since the relevant documents for this query are the documents with id 0 and 3, we should get recall equal to 0.5.\n\nquery_evaluation = evaluate_query(\n app=app,\n eval_metrics = eval_metrics, \n query_model = query_model, \n query_id = 0, \n query = \"Intrauterine virus infections and congenital heart disease\", \n id_field = \"id\",\n relevant_docs = [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}],\n default_score = 0,\n recall = (\"id\", [0, 1, 2])\n)\nquery_evaluation\n\n{'model': 'default_name',\n 'query_id': 0,\n 'match_ratio': 9.70242657688688e-06,\n 'recall_10': 0.5,\n 'reciprocal_rank_10': 1.0}\n\n\nWe now include documents with id equal to 0, 1, 2 and 3. This should give a recall equal to 1.\n\nquery_evaluation = evaluate_query(\n app=app,\n eval_metrics = eval_metrics, \n query_model = query_model, \n query_id = 0, \n query = \"Intrauterine virus infections and congenital heart disease\", \n id_field = \"id\",\n relevant_docs = [{\"id\": 0, \"score\": 1}, {\"id\": 3, \"score\": 1}],\n default_score = 0,\n recall = (\"id\", [0, 1, 2, 3])\n)\nquery_evaluation\n\n{'model': 'default_name',\n 'query_id': 0,\n 'match_ratio': 1.2936568769182506e-05,\n 'recall_10': 1.0,\n 'reciprocal_rank_10': 1.0}" + }, + { + "objectID": "passage_dataset.html", + "href": "passage_dataset.html", + "title": "Dataset", + "section": "", + "text": "For the passage ranking use case, we will use the MS MARCO passage dataset 1 through the ir_datasets library. Besides being convenient, ir_datasets solves encoding errors in the original dataset source files.\nimport ir_datasets\nimport pandas as pd" + }, + { + "objectID": "passage_dataset.html#data-exploration", + "href": "passage_dataset.html#data-exploration", + "title": "Dataset", + "section": "Data Exploration", + "text": "Data Exploration\n\nDocument corpus\nStart by loading the data. The dataset will be downloaded once and cached on disk for future use, so it takes a while the first time the command below is run.\n\npassage_corpus = ir_datasets.load(\"msmarco-passage\")\n\nNumber of passages in the document corpus:\n\npassage_corpus.docs_count()\n\n8841823\n\n\nSample a few passages of the document corpus.\n\npd.DataFrame(passage_corpus.docs_iter()[0:5])\n\n\n\n\n\n\n\n\ndoc_id\ntext\n\n\n\n\n0\n0\nThe presence of communication amid scientific ...\n\n\n1\n1\nThe Manhattan Project and its atomic bomb help...\n\n\n2\n2\nEssay on The Manhattan Project - The Manhattan...\n\n\n3\n3\nThe Manhattan Project was the name for a proje...\n\n\n4\n4\nversions of each volume as well as complementa...\n\n\n\n\n\n\n\n\n\nTraining data\nLoad the training data. We use the judged version that only include queries with at least one relevance judgement.\n\npassage_train = ir_datasets.load(\"msmarco-passage/train/judged\")\n\n\nRelevant documents\nNumber of relevant judgements:\n\npassage_train.qrels_count()\n\n532761\n\n\nFor each query id, there is a dict of relevant documents containing the document id as key and the relevance score as value.\n\nfrom learntorank.passage import sample_dict_items\n\ntrain_qrels_dict = passage_train.qrels_dict()\nsample_dict_items(train_qrels_dict, 5)\n\n{'1038069': {'2293922': 1},\n '700425': {'4351261': 1},\n '926242': {'3500124': 1},\n '690553': {'2877918': 1},\n '411317': {'2230220': 1}}\n\n\nIt is interesting to check what is the range of values of the relevance score. The code below shows that the only score available is 1, indicating that the particular document id is relevant to the query id.\n\nset([score \n for relevant in train_qrels_dict.values() \n for score in relevant.values()]\n )\n\n{1}\n\n\n\n\nQueries\nNumber of training queries:\n\npassage_train.queries_count()\n\n502939\n\n\nThe number of queries differs from the number of relevant documents because some of the queries have more than one relevant document associated with it.\nEach query contains a query id and a query text.\n\ntraining_queries = pd.DataFrame(passage_train.queries_iter())\ntraining_queries.head()\n\n\n\n\n\n\n\n\nquery_id\ntext\n\n\n\n\n0\n121352\ndefine extreme\n\n\n1\n634306\nwhat does chattel mean on credit history\n\n\n2\n920825\nwhat was the great leap forward brainly\n\n\n3\n510633\ntattoo fixers how much does it cost\n\n\n4\n737889\nwhat is decentralization process.\n\n\n\n\n\n\n\n\n\n\nDevelopment data\nSimilarly to the training data, we can load the judged development data and take a look at the queries and relevance judgements.\n\npassage_dev = ir_datasets.load(\"msmarco-passage/dev/judged\")\n\n\nRelevant documents\nNumber of relevant judgements:\n\npassage_dev.qrels_count()\n\n59273\n\n\nFor each query id, there is a dict of relevant documents containing the document id as key and the relevance score as value.\n\ndev_qrels_dict = passage_dev.qrels_dict()\nsample_dict_items(dev_qrels_dict, 5)\n\n{'255': {'7629892': 1},\n '611327': {'7610137': 1},\n '584695': {'7408281': 1},\n '300246': {'7814106': 1, '7814107': 1},\n '739094': {'7640560': 1}}\n\n\n\n\nQueries\nNumber of dev queries:\n\npassage_dev.queries_count()\n\n55578\n\n\nEach query contains a query id and a query text.\n\ndev_queries = pd.DataFrame(passage_dev.queries_iter())\ndev_queries.head()\n\n\n\n\n\n\n\n\nquery_id\ntext\n\n\n\n\n0\n1048578\ncost of endless pools/swim spa\n\n\n1\n1048579\nwhat is pcnt\n\n\n2\n1048582\nwhat is paysky\n\n\n3\n1048583\nwhat is paydata\n\n\n4\n1048585\nwhat is paula deen's brother" + }, + { + "objectID": "passage_dataset.html#data-manipulation", + "href": "passage_dataset.html#data-manipulation", + "title": "Dataset", + "section": "Data Manipulation", + "text": "Data Manipulation\n\nSample data\nGiven the large amount of data, it is useful to properly sample data when prototyping, which can be done with the sample_data function. This might take same time in case the full dataset needs to be downloaded for the first time.\n\nfrom learntorank.passage import sample_data\n\npassage_sample = sample_data(n_relevant=100, n_irrelevant=800)\n\n\npassage_sample\n\nPassageData(corpus, train_qrels, train_queries, dev_qrels, dev_queries)\n\n\n\nSave\nWe can save the sampled data to disk to avoid regenerating it everytime we need to use it.\n\npassage_sample.save(\"sample.json\")\n\n\n\nLoad\nLoad the data back when needed with PassageData.load:\n\nfrom learntorank.passage import PassageData\n\nloaded_sample = PassageData.load(file_path=\"sample.json\")" + }, + { + "objectID": "passage_dataset.html#footnotes", + "href": "passage_dataset.html#footnotes", + "title": "Dataset", + "section": "Footnotes", + "text": "Footnotes\n\n\nMS MARCO: A Human Generated MAchine Reading COmprehension Dataset↩︎" + }, + { + "objectID": "index.html#motivation", + "href": "index.html#motivation", + "title": "Vespa for Data Scientists", + "section": "Motivation", + "text": "Motivation\nThis library contains application specific code related to data manipulation and analysis of different Vespa use cases. The Vespa python API is used to interact with Vespa applications from python for faster exploration.\nThe main goal of this space is to facilitate prototyping and experimentation for data scientists. Please visit Vespa sample apps for production-ready use cases and Vespa docs for in-depth Vespa documentation." + }, + { + "objectID": "index.html#install", + "href": "index.html#install", + "title": "Vespa for Data Scientists", + "section": "Install", + "text": "Install\nCode to support and reproduce the use cases documented here can be found in the learntorank library.\nInstall via PyPI:\npip install learntorank" + }, + { + "objectID": "index.html#development", + "href": "index.html#development", + "title": "Vespa for Data Scientists", + "section": "Development", + "text": "Development\nAll the code and content of this repo is created using nbdev by editing notebooks. We will give a summary below about the main points required to contribute, but we suggest going through nbdev tutorials to learn more.\n\nSetting up environment\n\nCreate and activate a virtual environment of your choice. We recommend pipenv.\npipenv shell\nInstall Jupyter Lab (or Jupyter Notebook if you prefer).\npip3 install jupyterlab\nCreate a new kernel for Jupyter that uses the virtual environment created at step 1.\n\nCheck where the current list of kernels is located with jupyter kernelspec list.\nCopy one of the existing folder and rename it to learntorank.\nModify the kernel.json file that is inside the new folder to reflect the python3executable associated with your virtual env.\n\nInstall nbdev library:\npip3 install nbdev\nInstall learntorank in development mode:\npip3 install -e .[dev]\n\n\n\nMost used nbdev commands\nFrom your terminal:\n\nnbdev_help: List all nbdev commands available.\nnbdev_readme: Update README.md based on index.ipynb\nPreview documentation while editing the notebooks:\n\nnbdev_preview --port 3000\n\nWorkflow before pushing code:\n\nnbdev_test --n_workers 2: Execute all the tests inside notebooks.\n\nTests can run in parallel but since we create Docker containers we suggest a low number of workers to preserve memory.\n\nnbdev_export: Export code from notebooks to the python library.\nnbdev_clean: Clean notebooks to avoid merge conflicts.\n\nPublish library\n\nnbdev_bump_version: Bump library version.\nnbdev_pypi: Publish library to PyPI." + }, + { + "objectID": "module_ranking.html", + "href": "module_ranking.html", + "title": "ranking", + "section": "", + "text": "source\n\nkeras_linear_model\n\n keras_linear_model (number_documents_per_query, number_features)\n\nlinear model with a lasso constrain on the kernel weights.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nnumber_documents_per_query\n\nNumber of documents per query to reshape the listwise prediction.\n\n\nnumber_features\n\nNumber of features used per document.\n\n\nReturns\nSequential\nThe uncompiled Keras model.\n\n\n\nUsage:\n\nklm = keras_linear_model(\n number_documents_per_query=10, \n number_features=5\n)\n\n\nsource\n\n\nkeras_lasso_linear_model\n\n keras_lasso_linear_model (number_documents_per_query, number_features,\n l1_penalty, normalization_layer:Optional=None)\n\nlinear model with a lasso constrain on the kernel weights.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nnumber_documents_per_query\n\n\nNumber of documents per query to reshape the listwise prediction.\n\n\nnumber_features\n\n\nNumber of features used per document.\n\n\nl1_penalty\n\n\nControls the L1-norm penalty.\n\n\nnormalization_layer\ntyping.Optional\nNone\nInitialized normalization layers. Used when performing feature selection.\n\n\nReturns\nSequential\n\nThe uncompiled Keras model.\n\n\n\nUsage:\n\nkllm = keras_lasso_linear_model(\n number_documents_per_query=10, \n number_features=5, \n l1_penalty=0.01\n)\n\n\nsource\n\n\nkeras_ndcg_compiled_model\n\n keras_ndcg_compiled_model (model, learning_rate, top_n)\n\nCompile listwise Keras model with NDCG stateless metric and ApproxNDCGLoss\n\n\n\n\nDetails\n\n\n\n\nmodel\nUncompiled Keras model\n\n\nlearning_rate\nLearning rate used in the Adagrad optim algo.\n\n\ntop_n\nTop n used when computing the NDCG metric\n\n\n\nUsage:\n\ncompiled_klm = keras_ndcg_compiled_model(\n model=klm, \n learning_rate=0.1, \n top_n=10\n)\n\n\nsource\n\n\nLinearHyperModel\n\n LinearHyperModel (number_documents_per_query, number_features, top_n=10,\n learning_rate_range=None)\n\nDefine a KerasTuner search space for linear models\n\nlinear_hyper_model = LinearHyperModel(\n number_documents_per_query=10, \n number_features=10, \n top_n=10, \n learning_rate_range=[1e-2, 1e2]\n)\n\n\nsource\n\n\nLassoHyperModel\n\n LassoHyperModel (number_documents_per_query, number_features,\n trained_normalization_layer, top_n=10,\n l1_penalty_range=None, learning_rate_range=None)\n\nDefine a KerasTuner search space for lasso models\n\nsource\n\n\nListwiseRankingFramework\n\n ListwiseRankingFramework (number_documents_per_query, batch_size=32,\n shuffle_buffer_size=1000, tuner_max_trials=3,\n tuner_executions_per_trial=1, tuner_epochs=1,\n tuner_early_stop_patience=None, final_epochs=1,\n top_n=10, l1_penalty_range=None,\n learning_rate_range=None, folder_dir='/home/run\n ner/work/learntorank/learntorank')\n\nListwise ranking framework" + } +] \ 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 0000000..94f1940 --- /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 0000000..18d21d4 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 0000000..30038d1 --- /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: 17px;--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--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:.25em;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.5;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.5;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.5 !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 0000000..cc0a255 --- /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 0000000..1103f81 --- /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 0000000..2269d66 --- /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 0000000..d9fd98f --- /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 0000000..e6ae635 --- /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 0000000..ca292be --- /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-nav/headroom.min.js b/site_libs/quarto-nav/headroom.min.js new file mode 100644 index 0000000..b08f1df --- /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 0000000..3b21201 --- /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 0000000..619c57c --- /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 0000000..adc2835 --- /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 0000000..f5d852d --- /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 0000000..98e4acf --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,79 @@ + + + + https://thigm85.github.io/learntorank/module_ml.html + 2023-09-21T11:19:55.015Z + + + https://thigm85.github.io/learntorank/module_passage.html + 2023-09-21T11:19:53.463Z + + + https://thigm85.github.io/learntorank/module_stats.html + 2023-09-21T11:19:51.943Z + + + https://thigm85.github.io/learntorank/module_query.html + 2023-09-21T11:19:50.019Z + + + https://thigm85.github.io/learntorank/passage_uncertainty_evaluation.html + 2023-09-21T11:19:45.791Z + + + https://thigm85.github.io/learntorank/notebooks/compare-pre-trained-clip-for-text-image-search.html + 2023-09-21T11:19:44.255Z + + + https://thigm85.github.io/learntorank/notebooks/cord19/cord19_connect_evaluate.html + 2023-09-21T11:19:42.167Z + + + https://thigm85.github.io/learntorank/notebooks/image_search/image-search-scratch.html + 2023-09-21T11:19:40.787Z + + + https://thigm85.github.io/learntorank/notebooks/tensorflow-via-onnx.html + 2023-09-21T11:19:39.427Z + + + https://thigm85.github.io/learntorank/stateless_sequence_classification_task.html + 2023-09-21T11:19:37.247Z + + + https://thigm85.github.io/learntorank/notebooks/query-model.html + 2023-09-21T11:19:38.235Z + + + https://thigm85.github.io/learntorank/notebooks/collect-training-data.html + 2023-09-21T11:19:40.203Z + + + https://thigm85.github.io/learntorank/notebooks/cord19/cord19_download_parse_trec_covid.html + 2023-09-21T11:19:41.363Z + + + https://thigm85.github.io/learntorank/notebooks/learning-to-rank-ignore.html + 2023-09-21T11:19:43.695Z + + + https://thigm85.github.io/learntorank/notebooks/evaluation.html + 2023-09-21T11:19:44.859Z + + + https://thigm85.github.io/learntorank/module_evaluation.html + 2023-09-21T11:19:47.667Z + + + https://thigm85.github.io/learntorank/passage_dataset.html + 2023-09-21T11:19:50.787Z + + + https://thigm85.github.io/learntorank/index.html + 2023-09-21T11:19:52.463Z + + + https://thigm85.github.io/learntorank/module_ranking.html + 2023-09-21T11:19:53.991Z + + diff --git a/stateless_sequence_classification_task.html b/stateless_sequence_classification_task.html new file mode 100644 index 0000000..533791b --- /dev/null +++ b/stateless_sequence_classification_task.html @@ -0,0 +1,683 @@ + + + + + + + + + + +Vespa Data Science - Sequence Classification task + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Sequence Classification task

+
+ +
+
+ Accelerated model evaluation using ONNX Runtime in the stateless cluster +
+
+ + +
+ + + + +
+ + +
+ + +

Vespa has implemented accelerated model evaluation using ONNX Runtime in the stateless cluster. This opens up new usage areas for Vespa, such as serving model predictions.

+
+

Define the model server

+

The SequenceClassification task takes a text input and returns an array of floats that depends on the model used to solve the task. The model argument can be the id of the model as defined by the huggingface model hub.

+
+
from learntorank.ml import SequenceClassification
+
+task = SequenceClassification(
+    model_id="bert_tiny", 
+    model="google/bert_uncased_L-2_H-128_A-2"
+)
+
+

A ModelServer is a simplified application package focused on stateless model evaluation. It can take as many tasks as we want.

+
+
from learntorank.ml import ModelServer
+
+model_server = ModelServer(
+    name="bertModelServer",
+    tasks=[task],
+)
+
+
+
+

Deploy the model server

+

We can either host our model server on Vespa Cloud or deploy it locally using a Docker container.

+
+
from vespa.deployment import VespaDocker
+
+vespa_docker = VespaDocker()
+app = vespa_docker.deploy(application_package=model_server)
+
+
Using framework PyTorch: 1.12.1
+Found input input_ids with shape: {0: 'batch', 1: 'sequence'}
+Found input token_type_ids with shape: {0: 'batch', 1: 'sequence'}
+Found input attention_mask with shape: {0: 'batch', 1: 'sequence'}
+Found output output_0 with shape: {0: 'batch'}
+Ensuring inputs are in correct order
+position_ids is not present in the generated input list.
+Generated inputs order: ['input_ids', 'attention_mask', 'token_type_ids']
+Waiting for configuration server, 0/300 seconds...
+Waiting for configuration server, 5/300 seconds...
+Waiting for application status, 0/300 seconds...
+Waiting for application status, 5/300 seconds...
+Finished deployment.
+
+
+
+
+

Get model information

+

Get models available:

+
+
app.get_model_endpoint()
+
+
{'bert_tiny': 'http://localhost:8080/model-evaluation/v1/bert_tiny'}
+
+
+

Get information about a specific model:

+
+
app.get_model_endpoint(model_id="bert_tiny")
+
+
{'model': 'bert_tiny',
+ 'functions': [{'function': 'output_0',
+   'info': 'http://localhost:8080/model-evaluation/v1/bert_tiny/output_0',
+   'eval': 'http://localhost:8080/model-evaluation/v1/bert_tiny/output_0/eval',
+   'arguments': [{'name': 'input_ids', 'type': 'tensor(d0[],d1[])'},
+    {'name': 'attention_mask', 'type': 'tensor(d0[],d1[])'},
+    {'name': 'token_type_ids', 'type': 'tensor(d0[],d1[])'}]}]}
+
+
+
+
+

Get predictions

+

Get a prediction:

+
+
app.predict(x="this is a test", model_id="bert_tiny")
+
+
[-0.00954509899020195, 0.2504960000514984]
+
+
+
+
+

Cleanup

+
+
from shutil import rmtree
+
+vespa_docker.container.stop(timeout=600)
+vespa_docker.container.remove()
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..b46c209 --- /dev/null +++ b/styles.css @@ -0,0 +1,18 @@ +.cell-output 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 { + background: none; + margin-top: 0; + } + + .cell > .sourceCode { + margin-bottom: 0; + } + \ No newline at end of file