diff --git a/.extras/Project.toml b/.extras/Project.toml new file mode 100644 index 00000000..7af2e2e8 --- /dev/null +++ b/.extras/Project.toml @@ -0,0 +1,10 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/.extras/vector_field.jl b/.extras/vector_field.jl new file mode 100644 index 00000000..c2387fd4 --- /dev/null +++ b/.extras/vector_field.jl @@ -0,0 +1,443 @@ +#!/usr/bin/env julia +using Revise +using Pkg + +# Add the project to the path +Pkg.activate(@__DIR__) +Pkg.develop(path=joinpath(@__DIR__, "..")) + +using CTFlows.Data +using CTFlows.Common +using CTFlows.Systems +using CTFlows.Flows +using CTFlows.Integrators +using CTFlows.Solutions +using OrdinaryDiffEqTsit5 + +println("=" ^ 80) +println("CTFlows v1 Examples") +println("=" ^ 80) + +# ============================================================================= +# 1. VectorField with Explicit Traits +# ============================================================================= + +println("\n1. VectorField with Explicit Traits") +println("-" ^ 80) + +# Using keyword constructor with defaults +vf_default = Data.VectorField(x -> -x) +println("Default constructor (is_autonomous=true, is_variable=false):") +display(vf_default) + +# Autonomous Fixed - depends only on state x +println("\n--- Scalar case ---") +vf_scalar = Data.VectorField(x -> -2x; is_autonomous=true, is_variable=false) +println("Scalar: vf(3.0) = ", vf_scalar(3.0)) +display(vf_scalar) + +println("\n--- Vector case ---") +vf_vector = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +println("Vector: vf([1.0, 2.0]) = ", vf_vector([1.0, 2.0])) +display(vf_vector) + +println("\n--- Matrix case ---") +vf_matrix = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +x0_matrix = [1.0 2.0; 3.0 4.0] +println("Matrix: vf(x0_matrix) = ", vf_matrix(x0_matrix)) +display(vf_matrix) + +# NonAutonomous Fixed - depends on time t and state x +println("\n--- NonAutonomous cases ---") +vf_nonautonomous_fixed = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) +println("NonAutonomous Fixed (vector): vf(2.0, [1.0, 2.0]) = ", vf_nonautonomous_fixed(2.0, [1.0, 2.0])) + +# Autonomous NonFixed - depends on state x and variable v +vf_autonomous_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) +println("Autonomous NonFixed (vector): vf([1.0, 2.0], 0.5) = ", vf_autonomous_nonfixed([1.0, 2.0], 0.5)) + +# NonAutonomous NonFixed - depends on time t, state x, and variable v +vf_nonautonomous_nonfixed = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) +println("NonAutonomous NonFixed (vector): vf(2.0, [1.0, 2.0], 0.5) = ", vf_nonautonomous_nonfixed(2.0, [1.0, 2.0], 0.5)) + +# Using keyword constructor with explicit flags +vf_kw_autonomous = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +println("\nKeyword constructor with explicit flags:") +display(vf_kw_autonomous) + +vf_kw_nonautonomous = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) +println("NonAutonomous via keyword:") +display(vf_kw_nonautonomous) + +vf_kw_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) +println("NonFixed via keyword:") +display(vf_kw_nonfixed) + +# ============================================================================= +# 2. VectorFieldSystem +# ============================================================================= + +println("\n2. VectorFieldSystem") +println("-" ^ 80) + +println("\n--- Autonomous Fixed ---") +sys_af = Systems.VectorFieldSystem(vf_vector) +println("System from Autonomous Fixed VectorField:") +println(" time_dependence(sys) = ", Common.time_dependence(sys_af)) +println(" variable_dependence(sys) = ", Common.variable_dependence(sys_af)) + +println("\n--- NonAutonomous Fixed ---") +vf_naf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) +sys_naf = Systems.VectorFieldSystem(vf_naf) +println("System from NonAutonomous Fixed VectorField:") +println(" time_dependence(sys) = ", Common.time_dependence(sys_naf)) +println(" variable_dependence(sys) = ", Common.variable_dependence(sys_naf)) + +println("\n--- Autonomous NonFixed ---") +vf_anf = Data.VectorField((x, v) -> v .* x; is_autonomous=true, is_variable=true) +sys_anf = Systems.VectorFieldSystem(vf_anf) +println("System from Autonomous NonFixed VectorField:") +println(" time_dependence(sys) = ", Common.time_dependence(sys_anf)) +println(" variable_dependence(sys) = ", Common.variable_dependence(sys_anf)) + +println("\n--- NonAutonomous NonFixed ---") +vf_nanf = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) +sys_nanf = Systems.VectorFieldSystem(vf_nanf) +println("System from NonAutonomous NonFixed VectorField:") +println(" time_dependence(sys) = ", Common.time_dependence(sys_nanf)) +println(" variable_dependence(sys) = ", Common.variable_dependence(sys_nanf)) + +# ============================================================================= +# 3. Pipeline: build_system +# ============================================================================= + +println("\n3. Pipeline: build_system") +println("-" ^ 80) + +# Build system directly from VectorField +sys_built = Systems.build_system(vf_vector) +println("Built system: ", typeof(sys_built)) + +# ============================================================================= +# 4. Config Objects (PointConfig, TrajectoryConfig) +# ============================================================================= + +println("\n4. Config Objects") +println("-" ^ 80) + +# PointConfig for single point integration +point_config = Common.StatePointConfig(0.0, [1.0, 0.0], 1.0) +display(point_config) + +# TrajectoryConfig for full trajectory +traj_config = Common.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) +display(traj_config) + +# ============================================================================= +# 5. Flow Construction +# ============================================================================= + +println("\n5. Flow Construction") +println("-" ^ 80) + +println("\n--- build_flow from system and integrator ---") +println("Step 1: Build system from VectorField") +vf_flow = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +sys_flow = Systems.build_system(vf_flow) +println("System: ", typeof(sys_flow)) + +println("\nStep 2: Create integrator") +integrator_flow = Integrators.SciML(abstol=1e-8) +println("Integrator: ", typeof(integrator_flow)) + +println("\nStep 3: Build flow") +flow_from_build = Flows.StateFlow(sys_flow, integrator_flow) +println("Flow: ", typeof(flow_from_build)) +println(" system(flow) = ", typeof(Flows.system(flow_from_build))) +println(" integrator(flow) = ", typeof(Flows.integrator(flow_from_build))) + +println("\n--- Flow constructor from VectorField ---") +println("Direct construction: Flow(vf; opts...)") +flow_direct = Flows.Flow(vf_flow; reltol=1e-8) +println("Flow: ", typeof(flow_direct)) +println(" system(flow) = ", typeof(Flows.system(flow_direct))) +println(" integrator(flow) = ", typeof(Flows.integrator(flow_direct))) +Flows.integrator(flow_direct) + +println("\n--- NonFixed flow construction ---") +vf_nonfixed_flow = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) +flow_nonfixed = Flows.Flow(vf_nonfixed_flow) +println("NonFixed Flow: ", typeof(flow_nonfixed)) +println(" variable_dependence(system(flow)) = ", Common.variable_dependence(Flows.system(flow_nonfixed))) + +# ============================================================================= +# 6. Complete Pipeline Examples with Tsit5 Integration +# ============================================================================= + +println("\n6. Complete Pipeline with Tsit5 Integration") +println("-" ^ 80) + +# Load SciML extension +using OrdinaryDiffEqTsit5 + +println("\n--- Vector case pipeline (Fixed) ---") +println("Step 1: Create VectorField") +vf_vector = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +display(vf_vector) +println("Call: vf([1.0, 2.0]) = ", vf_vector([1.0, 2.0])) + +println("\nStep 2: Build System") +sys_vector = Systems.build_system(vf_vector) +println("System: ", typeof(sys_vector)) +println(" time_dependence(sys) = ", Common.time_dependence(sys_vector)) +println(" variable_dependence(sys) = ", Common.variable_dependence(sys_vector)) + +println("\nStep 3: Create Integrator") +integrator = Integrators.SciML() +println("Integrator: ", typeof(integrator)) + +println("\nStep 4: Integration via call()") +println("\n 4a. call(flow, config) with PointConfig") +flow = Flows.StateFlow(sys_vector, integrator) +config_point = Common.StatePointConfig(0.0, [1.0, 2.0], 1.0) +result_point = Flows.call(flow, config_point; variable=nothing, unsafe=false) +println(" result = ", result_point) + +println("\n 4b. call(flow, config) with TrajectoryConfig") +config_traj = Common.StateTrajectoryConfig((0.0, 1.0), [1.0, 2.0]) +result_traj = Flows.call(flow, config_traj; variable=nothing, unsafe=false) +println(" result type: ", typeof(result_traj)) +println(" result is VectorFieldSolution: ", result_traj isa Solutions.VectorFieldSolution) +display(result_traj) + +# Load Plots extension for plotting +using Plots +println("\n --- Plotting with Plots extension ---") +plot(result_traj) # Uses CTFlowsPlotsExt +result_traj(0.5) # Evaluate at t=0.5 using extension + +println("\n 4c. Direct flow callable (builds config internally)") +result_direct = flow(0.0, [1.0, 2.0], 1.0) +println(" flow(0.0, [1.0, 2.0], 1.0) = ", result_direct) + +println("\n--- NonFixed case (with variable) ---") +vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) +println("VectorField (NonFixed):") +display(vf_nonfixed) + +sys_nonfixed = Systems.build_system(vf_nonfixed) +flow_nonfixed = Flows.StateFlow(sys_nonfixed, integrator) +config_nonfixed = Common.StatePointConfig(0.0, [1.0, 2.0], 1.0) +result_nonfixed = Flows.call(flow_nonfixed, config_nonfixed; variable=0.5, unsafe=false) +println("Result with variable=0.5: ", result_nonfixed) + +println("\n Direct flow callable with variable:") +result_direct_nonfixed = flow_nonfixed(0.0, [1.0, 2.0], 1.0; variable=0.5, unsafe=false) +println(" flow(0.0, [1.0, 2.0], 1.0; variable=0.5) = ", result_direct_nonfixed) + +println("\n--- Scalar case ---") +vf_scalar = Data.VectorField(x -> -2x; is_autonomous=true, is_variable=false) +println("Scalar VectorField:") +display(vf_scalar) +sys_scalar = Systems.build_system(vf_scalar) +flow_scalar = Flows.StateFlow(sys_scalar, integrator) +config_scalar = Common.StatePointConfig(0.0, 3.0, 1.0) +result_scalar = Flows.call(flow_scalar, config_scalar; variable=nothing, unsafe=false) +println(" Result: ", result_scalar, " (scalar)") + +println("\n--- Matrix case ---") +vf_matrix = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +x0_matrix = [1.0 2.0; 3.0 4.0] +println("Matrix VectorField:") +display(vf_matrix) +sys_matrix = Systems.build_system(vf_matrix) +flow_matrix = Flows.StateFlow(sys_matrix, integrator) +config_matrix = Common.StatePointConfig(0.0, x0_matrix, 1.0) +result_matrix = Flows.call(flow_matrix, config_matrix; variable=nothing, unsafe=false) +println(" Result: ", result_matrix, " (matrix)") + +# ============================================================================= +# 6. Trait Information +# ============================================================================= + +println("\n6. Trait Information") +println("-" ^ 80) + +println("Available traits:") +println(" TimeDependence: Autonomous, NonAutonomous") +println(" VariableDependence: Fixed, NonFixed") + +println("\nTrait types are concrete structs for type parameter compatibility") +println(" Common.Autonomous (type, not instance)") +println(" Common.Fixed (type, not instance)") + +println("\n--- Trait accessors ---") +println(" time_dependence(vf) returns the time dependence trait") +println(" variable_dependence(vf) returns the variable dependence trait") +println(" time_dependence(sys) returns the time dependence trait from system") +println(" variable_dependence(sys) returns the variable dependence trait from system") + +# ============================================================================= +# 8. Summary +# ============================================================================= + +println("\n8. Summary") +println("-" ^ 80) + +println("To execute the complete pipeline with Flow:") +println(" 1. Install OrdinaryDiffEq: Pkg.add(\"OrdinaryDiffEqTsit5\")") +println(" 2. The CTFlowsSciMLExt extension will be automatically activated") +println(" 3. Then you can use:") +println(" integrator = Integrators.SciML()") +println(" flow = Flows.StateFlow(system, integrator)") +println(" result = Flows.call(flow, config; variable=nothing, unsafe=false)") +println(" result = flow(t0, x0, tf) # direct callable, builds config internally") + +# ============================================================================= +# 9. Multi-Phase Concatenation with Jumps +# ============================================================================= + +println("\n9. Multi-Phase Concatenation with Jumps") +println("-" ^ 80) + +using CTFlows.MultiPhase +using Plots + +println("\n--- Example 1: Two-phase flow without jump ---") +println("Linear system: dx/dt = -x, solution: x(t) = x0 * exp(-t)") + +vf_linear = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +sys_linear = Systems.build_system(vf_linear) +integ_linear = Integrators.SciML() +flow_linear = Flows.StateFlow(sys_linear, integ_linear) + +# Create two-phase flow +mpf_two_phase = flow_linear * (0.5, flow_linear) + +println("Two-phase flow: f * (0.5, f)") +println(" Phase 1: [0.0, 0.5]") +println(" Phase 2: [0.5, 1.0]") + +# Point integration +x0 = [1.0] +xf_two = mpf_two_phase(0.0, x0, 1.0) +println(" Final state: x(1.0) = ", xf_two[1]) +println(" Expected: exp(-1.0) = ", exp(-1.0)) + +# Trajectory integration +sol_two = mpf_two_phase((0.0, 1.0), x0) +println(" Solution type: ", typeof(sol_two)) +println(" Time points: ", Integrators.times(sol_two)) + +# Plot two-phase trajectory +p_two = plot(Integrators.times(sol_two), [u[1] for u in sol_two.(Integrators.times(sol_two))], + label="Two-phase (no jump)", title="Two-Phase Trajectory", + xlabel="t", ylabel="x(t)", linewidth=2, marker=:circle, markersize=3) +display(p_two) + +println("\n--- Example 2: Two-phase flow with jump ---") +jump_value = 5.0 +mpf_jump = flow_linear * (0.5, jump_value, flow_linear) + +println("Two-phase flow with jump: f * (0.5, 5.0, f)") +println(" Phase 1: [0.0, 0.5]") +println(" Jump: +5.0 at t=0.5") +println(" Phase 2: [0.5, 1.0]") + +# Point integration +xf_jump = mpf_jump(0.0, x0, 1.0) +expected_jump = (exp(-0.5) + jump_value) * exp(-0.5) +println(" Final state: x(1.0) = ", xf_jump[1]) +println(" Expected: (exp(-0.5) + 5.0) * exp(-0.5) = ", expected_jump) + +# Trajectory integration +sol_jump = mpf_jump((0.0, 1.0), x0) + +# Plot two-phase trajectory with jump +p_jump = plot(Integrators.times(sol_jump), [u[1] for u in sol_jump.(Integrators.times(sol_jump))], + label="Two-phase with jump", title="Two-Phase Trajectory with Jump", + xlabel="t", ylabel="x(t)", linewidth=2, marker=:circle, markersize=3) +display(p_jump) + +# Compare both trajectories +p_compare = plot(Integrators.times(sol_two), [u[1] for u in sol_two.(Integrators.times(sol_two))], + label="No jump", linewidth=2, marker=:circle, markersize=3) +plot!(Integrators.times(sol_jump), [u[1] for u in sol_jump.(Integrators.times(sol_jump))], + label="With jump (+5.0)", linewidth=2, marker=:diamond, markersize=3) +title!("Comparison: With vs Without Jump") +xlabel!("t") +ylabel!("x(t)") +display(p_compare) + +println("\n--- Example 3: Three-phase flow with multiple jumps ---") +mpf_three = flow_linear * (0.3, 2.0, flow_linear) * (0.6, 3.0, flow_linear) + +println("Three-phase flow: f * (0.3, 2.0, f) * (0.6, 3.0, f)") +println(" Phase 1: [0.0, 0.3]") +println(" Jump 1: +2.0 at t=0.3") +println(" Phase 2: [0.3, 0.6]") +println(" Jump 2: +3.0 at t=0.6") +println(" Phase 3: [0.6, 1.0]") + +# Point integration +xf_three = mpf_three(0.0, x0, 1.0) +expected_three = ((1.0 * exp(-0.3) + 2.0) * exp(-0.3) + 3.0) * exp(-0.4) +println(" Final state: x(1.0) = ", xf_three[1]) +println(" Expected: ((exp(-0.3) + 2.0) * exp(-0.3) + 3.0) * exp(-0.4) = ", expected_three) + +# Trajectory integration +sol_three = mpf_three((0.0, 1.0), x0) + +# Plot three-phase trajectory +p_three = plot(Integrators.times(sol_three), [u[1] for u in sol_three.(Integrators.times(sol_three))], + label="Three-phase with jumps", title="Three-Phase Trajectory with Multiple Jumps", + xlabel="t", ylabel="x(t)", linewidth=2, marker=:circle, markersize=3) +display(p_three) + +# Compare all three trajectories +p_all = plot(Integrators.times(sol_two), [u[1] for u in sol_two.(Integrators.times(sol_two))], + label="Two-phase (no jump)", linewidth=2, marker=:circle, markersize=3) +plot!(Integrators.times(sol_jump), [u[1] for u in sol_jump.(Integrators.times(sol_jump))], + label="Two-phase (+5.0)", linewidth=2, marker=:diamond, markersize=3) +plot!(Integrators.times(sol_three), [u[1] for u in sol_three.(Integrators.times(sol_three))], + label="Three-phase (+2.0, +3.0)", linewidth=2, marker=:square, markersize=3) +title!("Comparison: All Multi-Phase Trajectories") +xlabel!("t") +ylabel!("x(t)") +display(p_all) + +println("\n--- Example 4: Trajectory with zero jump (should be same as no jump) ---") +mpf_zero = flow_linear * (0.5, 0.0, flow_linear) + +println("Two-phase flow with zero jump: f * (0.5, 0.0, f)") +xf_zero = mpf_zero(0.0, x0, 1.0) +println(" Final state: x(1.0) = ", xf_zero[1]) +println(" Expected: exp(-1.0) = ", exp(-1.0)) +println(" Match: ", isapprox(xf_zero[1], exp(-1.0), atol=1e-10)) + +println("\n--- Example 5: Switch after final time (should be ignored) ---") +mpf_after = flow_linear * (2.0, flow_linear) + +println("Two-phase flow with switch after final time: f * (2.0, f)") +println(" Switch at t=2.0, but final time is 1.0") +xf_after = mpf_after(0.0, x0, 1.0) +println(" Final state: x(1.0) = ", xf_after[1]) +println(" Expected: exp(-1.0) = ", exp(-1.0)) +println(" Match: ", isapprox(xf_after[1], exp(-1.0), atol=1e-10)) + +println("\n--- Example 6: Trajectory with discontinuity verification ---") +mpf_discontinuity = flow_linear * (0.5, 5.0, flow_linear) +sol_discontinuity = mpf_discontinuity((0.0, 1.0), x0) + +t_switch = 0.5 +u_before = sol_discontinuity(t_switch - 1e-6) +u_after = sol_discontinuity(t_switch + 1e-6) + +println("Verify discontinuity at t=0.5:") +println(" u(0.5 - ฮต) = ", u_before[1]) +println(" u(0.5 + ฮต) = ", u_after[1]) +println(" Jump magnitude: ", u_after[1] - u_before[1]) +println(" Expected jump: 5.0") + +println("\nAll multi-phase examples completed successfully!") + diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index fe1c7c41..55133632 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -7,3 +7,5 @@ on: jobs: call: uses: control-toolbox/CTActions/.github/workflows/spell-check.yml@main + with: + config-path: '_typos.toml' diff --git a/.gitignore b/.gitignore index ca0d7266..ab59ad50 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ Manifest*.toml # .reports/ -.windsurf/ \ No newline at end of file +.extras/ +# .windsurf/ +# AGENTS.md \ No newline at end of file diff --git a/.windsurf/rules/architecture.md b/.windsurf/rules/architecture.md new file mode 100644 index 00000000..61238328 --- /dev/null +++ b/.windsurf/rules/architecture.md @@ -0,0 +1,678 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Architecture and Design Principles + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“‹ **Applying Architecture Rule**: [specific principle being applied]" + +This ensures transparency about which architectural principle is being used and why. + +--- + +This document defines architecture and design principles for Julia code. These principles ensure code is maintainable, extensible, and follows best practices. + +## Core Principles + +1. **SRP** โ€” Each module, function, and type has one clear purpose +2. **OCP** โ€” Open for extension, closed for modification +3. **LSP** โ€” Subtypes must honor parent contracts +4. **ISP** โ€” Keep interfaces small and focused +5. **DIP** โ€” Depend on abstractions, not concrete implementations +6. **DRY** โ€” No code duplication +7. **KISS** โ€” Prefer simple solutions +8. **YAGNI** โ€” Don't add functionality until actually needed + +--- + +## SOLID Principles in Julia + +### Single Responsibility Principle (SRP) + +Every module, function, and type should have a single, well-defined responsibility. + +**โœ… Good - Focused responsibilities:** + +```julia +# Parsing responsibility +function parse_ocp_input(text::String) + return parsed_data +end + +# Validation responsibility +function validate_ocp_data(data) + return is_valid, errors +end + +# Processing responsibility +function solve_ocp(data) + return solution +end +``` + +**โŒ Bad - Too many responsibilities:** + +```julia +function handle_ocp(text::String) + parsed = parse(text) # Parsing + validate(parsed) # Validation + solution = solve(parsed) # Processing + save_to_file(solution, "out") # I/O + return format_output(solution) # Formatting +end +``` + +**Red flags:** + +- Function names with "and" or "or" +- Functions longer than 50 lines +- Multiple `if-else` branches handling different concerns +- Modules mixing unrelated functionality + +--- + +### Open/Closed Principle (OCP) + +Software should be open for extension but closed for modification. + +**โœ… Good - Extensible via abstract types:** + +```julia +# Define abstract interface +abstract type AbstractOptimizationProblem end + +# Existing implementation +struct LinearProblem <: AbstractOptimizationProblem + A::Matrix + b::Vector +end + +# Solver works with any AbstractOptimizationProblem +function solve(problem::AbstractOptimizationProblem) + # Generic solving logic +end + +# NEW: Extend without modifying existing code +struct NonlinearProblem <: AbstractOptimizationProblem + f::Function + x0::Vector +end +# Solver automatically works via multiple dispatch +``` + +**โŒ Bad - Hard-coded type checks:** + +```julia +function solve(problem) + if problem isa LinearProblem + # Linear solving + elseif problem isa NonlinearProblem + # Nonlinear solving + # Need to modify for every new type! + end +end +``` + +**How to apply:** + +- Use abstract types to define interfaces +- Leverage multiple dispatch for extensibility +- Avoid type checking with `isa` or `typeof` +- Design type hierarchies that allow new subtypes + +--- + +### Liskov Substitution Principle (LSP) + +Subtypes must be substitutable for their parent types without breaking functionality. + +**โœ… Good - Consistent interface:** + +```julia +abstract type AbstractModel end + +# Contract: all models must implement `evaluate` +function evaluate(model::AbstractModel, x) + throw(NotImplemented("evaluate not implemented for $(typeof(model))")) +end + +# Subtype honors contract +struct LinearModel <: AbstractModel + coeffs::Vector +end + +function evaluate(model::LinearModel, x) + return dot(model.coeffs, x) # Returns a number +end + +# Generic code works with any AbstractModel +function optimize(model::AbstractModel, x0) + value = evaluate(model, x0) # Safe for any model + # ... +end +``` + +**โŒ Bad - Subtype breaks contract:** + +```julia +struct BrokenModel <: AbstractModel + data::String +end + +function evaluate(model::BrokenModel, x) + return "error: invalid" # Returns String, not number! +end + +# This breaks unexpectedly +function optimize(model::AbstractModel, x0) + value = evaluate(model, x0) + gradient = value * 2 # ERROR if value is String! +end +``` + +**How to apply:** + +- Define clear contracts for abstract types (via docstrings) +- Ensure all subtypes implement required methods consistently +- Return types should be compatible across hierarchy +- Test that generic code works with all subtypes + +**Testing LSP:** + +```julia +@testset "Liskov Substitution" begin + # Test that all subtypes work with generic code + for ModelType in [LinearModel, QuadraticModel, CustomModel] + model = ModelType(test_params...) + @test evaluate(model, x) isa Number + @test optimize(model, x0) isa Solution + end +end +``` + +--- + +### Interface Segregation Principle (ISP) + +Keep interfaces small and focused. Don't force clients to depend on methods they don't use. + +**โœ… Good - Small, focused interfaces:** + +```julia +# Separate capabilities +abstract type Evaluable end +abstract type Differentiable end + +# Types implement only what they need +struct SimpleFunction <: Evaluable + f::Function +end + +struct SmoothFunction <: Union{Evaluable, Differentiable} + f::Function + df::Function +end + +# Clients depend only on what they need +function plot_function(f::Evaluable, xs) + return [evaluate(f, x) for x in xs] +end + +function optimize(f::Differentiable, x0) + return gradient_descent(f, x0) +end +``` + +**โŒ Bad - Bloated interface:** + +```julia +# Forces all types to implement everything +abstract type MathFunction end + +# Required methods (even if not needed): +evaluate(f::MathFunction, x) = error("not implemented") +gradient(f::MathFunction, x) = error("not implemented") +hessian(f::MathFunction, x) = error("not implemented") +integrate(f::MathFunction, a, b) = error("not implemented") + +# Simple function forced to implement everything +struct SimpleFunction <: MathFunction + f::Function +end + +evaluate(sf::SimpleFunction, x) = sf.f(x) +gradient(sf::SimpleFunction, x) = error("not differentiable") # Forced! +hessian(sf::SimpleFunction, x) = error("not differentiable") # Forced! +integrate(sf::SimpleFunction, a, b) = error("not integrable") # Forced! +``` + +**How to apply:** + +- Create small, focused abstract types +- Use `Union` types for multiple interfaces +- Don't force implementations of unused methods +- Export only necessary functions + +--- + +### Dependency Inversion Principle (DIP) + +Depend on abstractions, not concrete implementations. + +**โœ… Good - Depend on abstractions:** + +```julia +# High-level abstraction +abstract type DataStore end + +# High-level module depends on abstraction +struct DataProcessor + store::DataStore # Abstract type +end + +function process(dp::DataProcessor, data) + save(dp.store, data) # Works with any DataStore +end + +# Low-level implementations +struct FileStore <: DataStore + path::String +end + +struct DatabaseStore <: DataStore + connection::DBConnection +end + +# Easy to swap implementations +processor1 = DataProcessor(FileStore("data.txt")) +processor2 = DataProcessor(DatabaseStore(conn)) +``` + +**โŒ Bad - Depend on concrete types:** + +```julia +# Tightly coupled to file system +struct DataProcessor + file_path::String +end + +function process(dp::DataProcessor, data) + write(dp.file_path, data) # Hard-coded to files +end + +# Can't switch to database without modifying DataProcessor +``` + +**How to apply:** + +- Define abstract types for dependencies +- Pass abstract types as arguments +- Use dependency injection +- Avoid hard-coding concrete types + +--- + +## Other Design Principles + +### DRY - Don't Repeat Yourself + +Avoid code duplication. Every piece of knowledge should have a single representation. + +**โœ… Good - Extract common logic:** + +```julia +function validate_positive(x, name) + x > 0 || throw(IncorrectArgument("$name must be positive")) +end + +function create_model(n::Int, m::Int) + validate_positive(n, "n") + validate_positive(m, "m") + return Model(n, m) +end +``` + +**โŒ Bad - Duplicated validation:** + +```julia +function create_model(n::Int, m::Int) + n > 0 || throw(ArgumentError("n must be positive")) + m > 0 || throw(ArgumentError("m must be positive")) + return Model(n, m) +end + +function create_problem(n::Int, m::Int) + n > 0 || throw(ArgumentError("n must be positive")) # Duplicated! + m > 0 || throw(ArgumentError("m must be positive")) # Duplicated! + return Problem(n, m) +end +``` + +--- + +### KISS - Keep It Simple, Stupid + +Prefer simple solutions over complex ones. Avoid over-engineering. + +**โœ… Good - Simple and clear:** + +```julia +function compute_mean(xs) + return sum(xs) / length(xs) +end +``` + +**โŒ Bad - Over-engineered:** + +```julia +function compute_mean(xs) + accumulator = zero(eltype(xs)) + counter = 0 + for x in xs + accumulator = accumulator + x + counter = counter + 1 + end + return accumulator / counter +end +``` + +--- + +### YAGNI - You Aren't Gonna Need It + +Don't add functionality until it's actually needed. + +**โœ… Good - Implement what's needed:** + +```julia +struct Model + coeffs::Vector{Float64} +end + +function evaluate(m::Model, x) + return dot(m.coeffs, x) +end +``` + +**โŒ Bad - Premature features:** + +```julia +struct Model + coeffs::Vector{Float64} + cache::Dict{Vector, Float64} # Not needed yet + optimization_history::Vector # Not needed yet + metadata::Dict{Symbol, Any} # Not needed yet + version::String # Not needed yet +end +``` + +--- + +## Julia-Specific Patterns + +### Multiple Dispatch + +Use multiple dispatch for extensibility and clarity: + +```julia +# Define behavior for different type combinations +function combine(a::Number, b::Number) + return a + b +end + +function combine(a::Vector, b::Vector) + return vcat(a, b) +end + +function combine(a::String, b::String) + return a * b +end + +# Extensible: add new methods without modifying existing code +``` + +--- + +### Type Hierarchies + +Design type hierarchies that reflect conceptual relationships: + +```julia +# Clear hierarchy +abstract type AbstractStrategy end +abstract type AbstractDirectMethod <: AbstractStrategy end +abstract type AbstractIndirectMethod <: AbstractStrategy end + +struct DirectShooting <: AbstractDirectMethod end +struct DirectCollocation <: AbstractDirectMethod end +struct IndirectShooting <: AbstractIndirectMethod end +``` + +--- + +### Composition Over Inheritance + +Prefer composition (has-a) over inheritance (is-a) when appropriate: + +```julia +# Composition: Model has a solver +struct OptimizationModel + problem::AbstractProblem + solver::AbstractSolver + options::NamedTuple +end + +# Not: OptimizationModel <: AbstractSolver +``` + +--- + +### Parametric Types + +Use parametric types for type stability and flexibility: + +```julia +# Type-stable with parameters +struct Container{T} + items::Vector{T} +end + +# Flexible: works with any type +c1 = Container([1, 2, 3]) # Container{Int} +c2 = Container([1.0, 2.0, 3.0]) # Container{Float64} +``` + +--- + +## Module Organization + +### Layered Architecture + +Organize code in layers with clear dependencies: + +```text +Low-level (Core types, utilities) + โ†“ +Mid-level (Business logic, algorithms) + โ†“ +High-level (User-facing API, orchestration) +``` + +**Example:** + +```julia +# Low-level: Core types +module Types + abstract type AbstractProblem end + struct Problem <: AbstractProblem + # ... + end +end + +# Mid-level: Algorithms +module Solvers + using ..Types + function solve(p::AbstractProblem) + # ... + end +end + +# High-level: User API +module API + using ..Types + using ..Solvers + export solve, Problem +end +``` + +--- + +### Separation of Concerns + +Keep different concerns in separate modules: + +```julia +# Validation logic +module Validation + function validate_dimensions(n, m) + # ... + end +end + +# Parsing logic +module Parsing + function parse_input(text) + # ... + end +end + +# Business logic +module Core + using ..Validation + using ..Parsing + # ... +end +``` + +--- + +## Quality Checklist + +Before finalizing code, verify: + +- [ ] Each function has a single, clear responsibility +- [ ] Abstract types define clear interfaces +- [ ] Subtypes honor parent contracts (LSP) +- [ ] No hard-coded type checks (`isa`, `typeof`) +- [ ] Dependencies are on abstractions, not concrete types +- [ ] No code duplication (DRY) +- [ ] Solution is as simple as possible (KISS) +- [ ] No premature features (YAGNI) +- [ ] Multiple dispatch used appropriately +- [ ] Type hierarchies reflect conceptual relationships +- [ ] Module organization follows layered architecture + +--- + +## Common Anti-Patterns + +### God Object + +**โŒ Avoid:** One object that does everything + +```julia +struct System + data::Dict + config::Dict + state::Dict + # 50+ fields +end + +# 100+ methods operating on System +``` + +**โœ… Instead:** Split into focused components + +```julia +struct DataManager + data::Dict +end + +struct ConfigManager + config::Dict +end + +struct StateManager + state::Dict +end +``` + +--- + +### Primitive Obsession + +**โŒ Avoid:** Using primitives instead of domain types + +```julia +function create_problem(n::Int, m::Int, t0::Float64, tf::Float64) + # What do these numbers mean? +end +``` + +**โœ… Instead:** Use domain types + +```julia +struct Dimensions + state::Int + control::Int +end + +struct TimeInterval + initial::Float64 + final::Float64 +end + +function create_problem(dims::Dimensions, time::TimeInterval) + # Clear meaning +end +``` + +--- + +### Feature Envy + +**โŒ Avoid:** Methods that use more of another type's data + +```julia +function compute_cost(model::Model, data::Data) + # Uses mostly data fields, not model fields + return data.a * data.b + data.c +end +``` + +**โœ… Instead:** Move method to appropriate type + +```julia +function compute_cost(data::Data) + return data.a * data.b + data.c +end +``` + +--- + +## References + +- [Julia Style Guide](https://docs.julialang.org/en/v1/manual/style-guide/) +- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) +- [Design Patterns in Julia](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md) + +--- + +## Related Rules + +- `.windsurf/rules/docstrings.md` - Documentation standards +- `.windsurf/rules/testing.md` - Testing standards +- `.windsurf/rules/type-stability.md` - Type stability standards diff --git a/.windsurf/rules/docstrings.md b/.windsurf/rules/docstrings.md new file mode 100644 index 00000000..afee6911 --- /dev/null +++ b/.windsurf/rules/docstrings.md @@ -0,0 +1,352 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Documentation Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“š **Applying Documentation Rule**: [specific documentation principle being applied]" + +This ensures transparency about which documentation standard is being used and why. + +--- + +This document defines the documentation standards for the Control Toolbox project. All Julia code (functions, structs, macros, modules) must be documented following these guidelines. + +## Core Principles + +1. **Completeness**: Every exported symbol and significant internal component must have a docstring +2. **Accuracy**: Documentation must reflect actual behavior, not aspirational or outdated information +3. **Clarity**: Write for users who understand Julia but may be unfamiliar with the specific domain +4. **Consistency**: Follow the templates and conventions defined here + +## Docstring Placement + +- Docstrings go **immediately above** the declaration they document +- No blank lines between docstring and declaration +- For multi-method functions, document the most general signature or provide method-specific docstrings + +## Required Docstring Structure + +Every docstring should contain: + +1. **Signature line** (for functions): Use `$(TYPEDSIGNATURES)` from DocStringExtensions +2. **One-sentence summary**: Clear, concise description of purpose +3. **Detailed description** (if needed): Explain behavior, constraints, invariants, edge cases +4. **Structured sections** (as applicable): + - `# Arguments`: For functions/macros + - `# Fields`: For structs/types + - `# Returns`: For functions that return values + - `# Throws`: For functions that may throw exceptions + - `# Example` or `# Examples`: Demonstrate usage + - `# Notes`: Performance considerations, stability warnings, implementation details + - `# References`: Citations to papers, algorithms, or external documentation + - `See also:`: Related functions/types with `[@ref]` links + +## Cross-References + +### Internal References + +For symbols within the current package or its dependencies, use `[@ref]` syntax with **full module path** including the root package and submodules: + +```julia +See also: [`CTFlows.Flows.build_flow`](@ref), [`CTFlows.Flows.AbstractFlow`](@ref) +``` + +**Rules for @ref:** + +1. Use full module path including root package (e.g., `CTFlows.Integrators.SciMLTag`, not just `SciMLTag`) +2. Include all nested submodules in the path +3. Only use for symbols documented in the current package's documentation + +**Examples:** + +โœ… **Correct internal references:** + +- [`CTFlows.Integrators.SciMLTag`](@ref) +- [`CTFlows.Flows.AbstractFlow`](@ref) +- [`CTFlows.Common.AbstractTag`](@ref) + +โŒ **Incorrect internal references:** + +- [`SciMLTag`](@ref) # Missing module qualification +- [`Integrators.SciMLTag`](@ref) # Missing root package name + +### External Package References + +For symbols in external packages that are not part of the current documentation build, use `[@extref]` syntax with the **full module path** including submodules: + +```julia +See also: [`CTSolvers.Options.OptionValue`](@extref) +``` + +**Rules for @extref:** + +1. Use the complete module path (e.g., `CTSolvers.Options.OptionValue`, not just `OptionValue`) +2. Include all submodules in the path +3. Only use for symbols that are not documented in the current package's documentation +4. Use when the symbol is from a dependency that has its own separate documentation + +**Examples:** + +โœ… **Correct external references:** + +- [`CTBase.Exceptions.IncorrectArgument`](@extref) +- [`CTSolvers.Options.OptionValue`](@extref) +- [`CTBase.Tags.Autonomous`](@extref) + +โŒ **Incorrect external references:** + +- [`OptionValue`](@extref) # Missing module path +- [`CTSolvers.OptionValue`](@ref) # Wrong syntax for external symbol + +**When to use which:** + +- Use `[@ref]` for symbols within CTFlows or its included documentation +- Use `[@extref]` for symbols from external packages with separate documentation (CTBase, CTSolvers) + +## CTFlows Submodule Qualification + +Public symbols are always accessed through their submodule, never through the root package: + +```julia +CTFlows.Flows.build_flow(...) # โœ… correct +CTFlows.build_flow(...) # โŒ wrong โ€” nothing exported at root level +``` + +### Exposed Submodules + +| Submodule | Exported symbols (examples) | +| --- | --- | +| `CTFlows.Common` | `AbstractTag`, `AbstractTrait`, `Autonomous`, `Fixed` | +| `CTFlows.Data` | `VectorField`, `HamiltonianVectorField` | +| `CTFlows.Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow` | +| `CTFlows.Integrators` | `AbstractIntegrator`, `AbstractIntegrationResult` | + +## Docstring Templates + +### Function Template + +```julia +""" +$(TYPEDSIGNATURES) + +One-sentence description of what the function does. + +Optional detailed explanation covering: +- Behavior and semantics +- Constraints and preconditions +- Common use cases or patterns + +# Arguments +- `arg1::Type1`: Description of first argument +- `arg2::Type2`: Description of second argument + +# Returns +- `ReturnType`: Description of return value + +# Throws +- `ExceptionType`: When and why this exception is thrown + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> result = function_name(arg1, arg2) +expected_output +\`\`\` + +# Notes +- Performance characteristics (if relevant) +- Thread safety (if relevant) +- Stability guarantees + +See also: [`CTFlows.Flows.related_function`](@ref), [`CTFlows.Flows.RelatedType`](@ref) +""" +function function_name(arg1::Type1, arg2::Type2)::ReturnType + # implementation +end +``` + +--- + +### Struct Template + +```julia +""" +$(TYPEDEF) + +One-sentence description of what this type represents. + +Optional detailed explanation covering: +- Purpose and design intent +- Invariants that must be maintained +- Relationship to other types + +# Fields +- `field1::Type1`: Description and constraints +- `field2::Type2`: Description and constraints + +# Constructor Validation + +Describe any validation performed by constructors (if applicable). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> obj = StructName(value1, value2) +StructName(...) + +julia> obj.field1 +value1 +\`\`\` + +# Notes +- Mutability status (if not obvious from declaration) +- Performance considerations + +See also: [`CTFlows.Flows.related_type`](@ref), [`CTFlows.Flows.constructor_function`](@ref) +""" +struct StructName{T} + field1::Type1 + field2::Type2 +end +``` + +--- + +### Abstract Type Template + +```julia +""" +$(TYPEDEF) + +One-sentence description of the abstraction. + +Detailed explanation of: +- What types should subtype this +- Contract/interface requirements for subtypes +- Common behavior across all subtypes + +# Interface Requirements + +List methods that subtypes must implement: +- `required_method(::SubType)`: Description + +# Example +\`\`\`julia-repl +julia> using CTFlows.Common + +julia> MyType <: AbstractTypeName +true +\`\`\` + +See also: [`CTFlows.Common.ConcreteSubtype1`](@ref), [`CTFlows.Common.ConcreteSubtype2`](@ref) +""" +abstract type AbstractTypeName end +``` + +--- + +## Example Safety Policy + +Examples in docstrings must be **safe and reproducible**: + +### โœ… Safe Examples + +- Pure computations with deterministic results +- Constructors with simple, valid inputs +- Queries on created objects +- Examples that start with `using CTFlows.Submodule` + +### โŒ Unsafe Examples + +- File system operations (reading/writing files) +- Network requests +- Database operations +- Git operations +- Non-deterministic behavior (random numbers without seed, timing-dependent code) +- Long-running computations (>1 second) +- Dependencies on external state or global variables + +### Fallback for Complex Cases + +If a safe, runnable example cannot be provided: + +- Use a plain code block (\`\`\`julia) instead of REPL block (\`\`\`julia-repl) +- Show usage patterns without claiming specific output +- Provide a conceptual sketch of how to use the API + +Example: + +```julia +# Example +\`\`\`julia +# Conceptual usage pattern +flow = CTFlows.Flows.build_flow(sys, integrator) +result = CTFlows.Flows.evaluate(flow, t0, tf) +\`\`\` +``` + +## Module Prefix Convention + +- **Exported symbols**: Use directly without module prefix + + ```julia-repl + julia> using CTFlows.Flows + julia> flow = build_flow(sys, integrator) # build_flow is exported + ``` + +- **Internal symbols**: Use module prefix + + ```julia-repl + julia> using CTFlows.Flows + julia> Flows.internal_function(...) # Not exported + ``` + +When a docstring is defined inside `src/Flows/flow.jl`, the public prefix shown in the docs is `CTFlows.Flows.`: + +```julia +""" +$(TYPEDSIGNATURES) + +Build a flow from a system and an integrator. + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Integrators.AbstractIntegrator`](@ref) +""" +function build_flow(sys::AbstractSystem, integrator::AbstractIntegrator) +``` + +## DocStringExtensions Macros + +This project uses [DocStringExtensions.jl](https://github.com/JuliaDocs/DocStringExtensions.jl): + +- `$(TYPEDEF)`: Auto-generates type signature for structs/abstract types +- `$(TYPEDSIGNATURES)`: Auto-generates function signature with types +- Use these instead of manually writing signatures + +## Quality Checklist + +Before finalizing a docstring, verify: + +- [ ] Docstring is directly above the declaration (no blank lines) +- [ ] Uses `$(TYPEDEF)` or `$(TYPEDSIGNATURES)` where applicable +- [ ] One-sentence summary is clear and accurate +- [ ] All arguments/fields are documented with types and descriptions +- [ ] Return value is documented (if applicable) +- [ ] Exceptions are documented (if thrown) +- [ ] Example is safe, runnable, and demonstrates typical usage +- [ ] Cross-references use `[@ref]` for internal symbols +- [ ] Cross-references use `[@extref]` for external symbols +- [ ] Full module paths are used (e.g., `CTFlows.Flows.build_flow`) +- [ ] No invented behavior or aspirational features +- [ ] Consistent with project style and terminology + +## Related Rules + +- `.windsurf/rules/architecture.md` - Architecture and design principles +- `.windsurf/rules/testing.md` - Testing standards +- `.windsurf/rules/type-stability.md` - Type stability standards diff --git a/.windsurf/rules/documentation.md b/.windsurf/rules/documentation.md new file mode 100644 index 00000000..0a52ab21 --- /dev/null +++ b/.windsurf/rules/documentation.md @@ -0,0 +1,464 @@ +--- +trigger: glob +glob: "docs/**/*" +--- + +# Julia API Documentation Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“– **Applying Documentation Rule**: [specific documentation principle being applied]" + +This ensures transparency about which documentation principle is being used and why. + +--- + +This document defines how the `docs/` directory of a Control Toolbox package is organised and built. It complements [`docstrings.md`](docstrings.md) (which covers *what* to write inside docstrings) by specifying *how* those docstrings are turned into a published documentation site via [Documenter.jl](https://documenter.juliadocs.org/) and [`CTBase.automatic_reference_documentation()`](https://control-toolbox.org/CTBase.jl/stable/guide/api-documentation.html). + +## Reference Implementations + +Three control-toolbox packages illustrate the spectrum of complexity. All three share the same skeleton; differences are stylistic. + +| Package | Scale | Notable features | +| --- | --- | --- | +| [`CTSolvers.jl/docs`](https://github.com/control-toolbox/CTSolvers.jl/tree/main/docs) | mid-weight package | Architecture page + Developer Guides + API Reference; `subdirectory="api"`; DocumenterMermaid | +| [`CTModels.jl/docs`](https://github.com/control-toolbox/CTModels.jl/tree/main/docs) | single package | Introduction + API Reference only; `subdirectory="."`; multiple extensions with `DocMeta.setdocmeta!`; Q&A-style Quick Start | +| [`OptimalControl.jl/docs`](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs) | meta-package | `DocumenterInterLinks` for cross-refs to dependencies; copies `Project.toml`/`Manifest.toml` to assets; documents only its Private API (Public is exposed via `@extref`) | + +## Core Principles + +1. **Auto-generated API reference** โ€” never hand-write API pages; use `CTBase.automatic_reference_documentation()`. +2. **One page per submodule** โ€” each submodule gets its own auto-generated API page. +3. **One page per loaded extension** โ€” each `Base.get_extension`-detected extension gets its own page when present. +4. **Public + private documented** โ€” both `public=true` and `private=true`, since users access internals via qualified paths (consistent with [`modules.md`](modules.md)). +5. **Hand-written guides separate from API** โ€” narrative guides live under `docs/src/guides/`; the API reference is generated. +6. **Index page is the entry point** โ€” `docs/src/index.md` provides admonitions, module table, guide links via `[@ref]`, and a Quick Start. +7. **Cross-references resolve at build time** โ€” every `[@extref]` in a docstring must be backed by an `InterLinks` entry in `make.jl`. + +## Directory Layout + +```text +docs/ +โ”œโ”€โ”€ Project.toml +โ”œโ”€โ”€ make.jl # entry point; uses with_api_reference() +โ”œโ”€โ”€ api_reference.jl # generate_api_reference() + with_api_reference() +โ”œโ”€โ”€ inventories/ # InterLinks fallback inventories (one per dependency) +โ”‚ โ”œโ”€โ”€ CTBase.toml +โ”‚ โ”œโ”€โ”€ CTModels.toml +โ”‚ โ””โ”€โ”€ CTSolvers.toml +โ””โ”€โ”€ src/ + โ”œโ”€โ”€ index.md # landing page + โ”œโ”€โ”€ architecture.md # narrative architecture page (optional) + โ”œโ”€โ”€ guides/ # hand-written guides + โ”‚ โ”œโ”€โ”€ implementing_a_modeler.md + โ”‚ โ”œโ”€โ”€ implementing_an_integrator.md + โ”‚ โ””โ”€โ”€ ... + โ””โ”€โ”€ api/ # auto-generated (cleaned up after build) +``` + +## Cross-Reference Infrastructure: DocumenterInterLinks + +For the `[@extref]` syntax (defined in [`docstrings.md`](docstrings.md)) to actually resolve at build time, `make.jl` must declare an `InterLinks` registry โ€” one entry per cross-referenced dependency: + +```julia +using DocumenterInterLinks + +links = InterLinks( + "CTBase" => ( + "https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml"), + ), + "CTModels" => ( + "https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml"), + ), + "CTSolvers" => ( + "https://control-toolbox.org/CTSolvers.jl/stable/", + "https://control-toolbox.org/CTSolvers.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTSolvers.toml"), + ), + # โ€ฆ one entry per dependency referenced via @extref +) +``` + +Each entry is a 3-tuple: stable docs URL, `objects.inv` URL (Sphinx-style inventory served by Documenter.jl), and a local TOML inventory under `docs/inventories/` used as fallback. Pass `links` to `makedocs` via the `plugins` argument: + +```julia +makedocs(; plugins=[links], ...) +``` + +This is what makes references like `` [`CTSolvers.Strategies.AbstractStrategy`](@extref) `` resolve to the dependency's published documentation. + +## `docs/make.jl` Template + +### Common skeleton + +```julia +pushfirst!(LOAD_PATH, joinpath(@__DIR__)) +pushfirst!(LOAD_PATH, joinpath(@__DIR__, "..")) + +using Documenter +using DocumenterInterLinks +using CTFlows +using CTBase +using Markdown +using MarkdownAST: MarkdownAST +# Optional: using DocumenterMermaid + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DocumenterReference extension (from CTBase) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) +if !isnothing(DocumenterReference) + DocumenterReference.reset_config!() +end + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DocMeta setup for the package and its extensions (loop pattern) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +Modules = [CTFlows, CTFlowsODE] # add extensions and dependencies as needed +for Module in Modules + isnothing(Module) && continue + isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && + DocMeta.setdocmeta!(Module, :DocTestSetup, :(using $Module); recursive=true) +end + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# InterLinks (only if @extref is used in docstrings) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +links = InterLinks( + "CTBase" => ("https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml")), + "CTModels" => ("https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml")), + "CTSolvers" => ("https://control-toolbox.org/CTSolvers.jl/stable/", + "https://control-toolbox.org/CTSolvers.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTSolvers.toml")), +) + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Paths and API reference generator +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +repo_url = "github.com/control-toolbox/CTFlows.jl" +src_dir = abspath(joinpath(@__DIR__, "..", "src")) +ext_dir = abspath(joinpath(@__DIR__, "..", "ext")) + +include("api_reference.jl") + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Build +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +with_api_reference(src_dir, ext_dir) do api_pages + makedocs(; + draft = false, + remotes = nothing, + warnonly = [:cross_references], + sitename = "CTFlows.jl", + plugins = [links], + format = Documenter.HTML(; + repolink = "https://" * repo_url, + prettyurls = false, + assets = [ + asset("https://control-toolbox.org/assets/css/documentation.css"), + asset("https://control-toolbox.org/assets/js/documentation.js"), + ], + ), + pages = [ + "Introduction" => "index.md", + "Architecture" => "architecture.md", + "Developer Guides" => [ + "Implementing a System" => "guides/implementing_a_system.md", + "Implementing a Flow Modeler" => "guides/implementing_a_flow_modeler.md", + "Implementing an ODE Integrator" => "guides/implementing_an_ode_integrator.md", + "Implementing an AD Backend" => "guides/implementing_an_ad_backend.md", + "Pipelines" => "guides/pipelines.md", + "Multi-phase Composition" => "guides/multi_phase_composition.md", + "Error Messages Reference" => "guides/error_messages.md", + ], + "API Reference" => api_pages, + ], + ) +end + +deploydocs(; repo=repo_url * ".git", devbranch="main") +``` + +### Variations + +`pages` structure: + +- **Light (CTModels-style)** โ€” `pages = ["Introduction" => "index.md", "API Reference" => api_pages]`. Use when there are no narrative guides. +- **Full (CTSolvers-style, recommended for CTFlows)** โ€” Architecture + guides + API Reference, as shown above. + +`warnonly` setting: + +- `warnonly = true` (CTSolvers) โ€” accept all build warnings. +- `warnonly = [:cross_references]` (CTModels, recommended for CTFlows) โ€” accept only cross-reference warnings. + +`prettyurls`: + +- `false` for local browsing during development. +- `true` for deployed documentation (omit or rely on default). + +## `docs/api_reference.jl` Template + +The file defines two public functions and one internal helper: + +- `generate_api_reference(src_dir, ext_dir) -> pages` โ€” builds the `pages` vector by calling `CTBase.automatic_reference_documentation` for each submodule and each loaded extension. +- `with_api_reference(f, src_dir, ext_dir)` โ€” wrapper that generates pages, calls `f(pages)`, then cleans up generated `.md` files via `_cleanup_pages` (in a `try/finally`). +- `_cleanup_pages(docs_src, pages)` โ€” recursive helper that deletes the auto-generated files after the build. + +### Submodule call + +```julia +CTBase.automatic_reference_documentation(; + subdirectory = "api", # "api" (CTSolvers) or "." (CTModels) + primary_modules = [ + CTFlows.Systems => src( + joinpath("Systems", "Systems.jl"), + joinpath("Systems", "abstract_system.jl"), + # ... all included files of the submodule + ), + ], + external_modules_to_document = [CTFlows], # include re-exported symbols + exclude = EXCLUDE_SYMBOLS, + public = true, + private = true, + title = "Systems", + title_in_menu = "Systems", + filename = "api_systems", # "api_*" (CTModels) or "*" (CTSolvers) +) +``` + +### Extension call (auto-detected) + +```julia +CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +if !isnothing(CTFlowsODE) + push!(pages, + CTBase.automatic_reference_documentation(; + subdirectory = "api", + primary_modules = [CTFlowsODE => ext("CTFlowsODE.jl")], + external_modules_to_document = [CTFlows], + exclude = EXCLUDE_SYMBOLS, + public = true, + private = true, + title = "ODE Extension", + title_in_menu = "ODE", + filename = "api_ext_ode", + ), + ) +end +``` + +### Variations on the API call + +- **`subdirectory`** โ€” `"api"` puts pages under `docs/src/api/`; `"."` puts them directly under `docs/src/`. Pick one and stay consistent. +- **`filename` prefix** โ€” `"api_systems"` (CTModels) makes auto-generated files visually distinct from hand-written ones; `"systems"` (CTSolvers) is fine when pages live under `api/`. +- **`external_modules_to_document`** โ€” set to `[CTFlows]` whenever a submodule re-exports symbols at package level (almost always). +- **`EXCLUDE_SYMBOLS`** โ€” start from `Symbol[:include, :eval]` and extend with package-specific noise (private macros, helper symbols leaked from `using`). + +### Cleanup helper + +```julia +function _cleanup_pages(docs_src::String, pages) + for p in pages + val = last(p) + if val isa AbstractString + fname = endswith(val, ".md") ? val : val * ".md" + full_path = joinpath(docs_src, fname) + if isfile(full_path) + rm(full_path) + println("Removed temporary API doc: $full_path") + end + elseif val isa AbstractVector + _cleanup_pages(docs_src, val) + end + end +end +``` + +### Meta-package variant (OptimalControl-style) + +When the package re-exports symbols from several control-toolbox dependencies, the public API is documented via `[@extref]` to those dependencies; the package itself only documents its **private** API: + +```julia +pages = [ + CTBase.automatic_reference_documentation(; + subdirectory = "api", + primary_modules = [ + OptimalControl => src(joinpath("helpers", "..."), ...) + ], + external_modules_to_document = [CTBase, CTModels, CTSolvers], + public = false, # public API lives in the dependencies + private = true, + title = "Private", + title_in_menu = "Private", + filename = "private", + ), +] +``` + +For CTFlows (single package), this variant does not apply โ€” it is documented here for completeness. + +### Optional: copy `Project.toml` / `Manifest.toml` to assets + +For packages where users may want to reproduce the exact documentation environment (typically the meta-package): + +```julia +mkpath(joinpath(@__DIR__, "src", "assets")) +cp(joinpath(@__DIR__, "Project.toml"), + joinpath(@__DIR__, "src", "assets", "Project.toml"); force=true) +cp(joinpath(@__DIR__, "Manifest.toml"), + joinpath(@__DIR__, "src", "assets", "Manifest.toml"); force=true) +``` + +Place these `cp` calls in `make.jl`, before `makedocs`. + +## `docs/src/index.md` Template + +Mandatory structure โ€” the *end* of the file (Documentation section + Quick Start) is what users land on first: + +````markdown +# CTFlows.jl + +```@meta +CurrentModule = CTFlows +``` + +The `CTFlows.jl` package is part of the [control-toolbox ecosystem](https://github.com/control-toolbox). +It provides the **flow layer** for optimal control problems: + +- **Systems** โ€” assembled callable objects (`AbstractSystem`) +- **Flows** โ€” system + integrator pairs (`AbstractFlow`) +- **Modelers** โ€” flow modeler strategies (`AbstractFlowModeler`) +- **Integrators** โ€” ODE integrator strategies (`AbstractODEIntegrator`) +- **AD Backends** โ€” automatic-differentiation strategies (`AbstractADBackend`) +- **Pipelines** โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` + +!!! info "CTFlows vs CTModels and CTSolvers" + **CTFlows** focuses on **flowing** dynamical systems associated with optimal control problems + (assembling systems, integrating ODEs, building solutions). + For **defining** the problems themselves, see [CTModels.jl](https://github.com/control-toolbox/CTModels.jl); + for **solving** them via discretisation and NLP, see [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl). + +!!! note + The root package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl) which aims + to provide tools to model and solve optimal control problems with ordinary differential equations + by direct and indirect methods, both on CPU and GPU. + +!!! warning "Qualified Module Access" + CTFlows does **not** export functions at the package level. All functions and types are + accessed via qualified module paths (consistent with the [submodule architecture](modules.md)): + + ```julia + using CTFlows + CTFlows.Systems.dimensions(sys) # โœ“ Qualified + CTFlows.Pipelines.build_system(input, m, ad) # โœ“ Qualified + ``` + +## Modules + +| Module | Purpose | +|--------|---------| +| `Core` | Shared types and utilities | +| `Systems` | `AbstractSystem`, concrete systems, `MultiPhaseSystem` | +| `Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow` | +| `Modelers` | `AbstractFlowModeler` and concrete modelers | +| `Integrators` | `AbstractODEIntegrator` and concrete integrators | +| `ADBackends` | `AbstractADBackend` and concrete backends | +| `Pipelines` | `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` | + +## Documentation + +### Developer Guides + +- [Architecture](@ref) โ€” module overview, type hierarchy, data flow +- [Implementing a System](@ref) โ€” `AbstractSystem` contract +- [Implementing a Flow Modeler](@ref) โ€” `AbstractFlowModeler` strategy +- [Implementing an ODE Integrator](@ref) โ€” `AbstractODEIntegrator` strategy +- [Implementing an AD Backend](@ref) โ€” `AbstractADBackend` strategy +- [Pipelines](@ref) โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` +- [Multi-phase Composition](@ref) โ€” `MultiPhaseSystem` and `MultiPhaseFlow` +- [Error Messages Reference](@ref) โ€” exception types with examples and fixes + +### API Reference + +Auto-generated documentation for all public and private symbols, organised by submodule. + +## Quick Start + +```julia +using CTFlows +using OrdinaryDiffEq # loads the ODE integration extension + +# Build a system from an OCP +sys = CTFlows.Pipelines.build_system((ocp, u), modeler, ad_backend) + +# Build a flow (system + integrator) +flow = CTFlows.Pipelines.build_flow(sys, integrator) + +# Integrate +sol = CTFlows.Pipelines.solve(flow, (t0, tf), x0, p0) +``` +```` + +### Quick Start variants + +- **Code-first (CTSolvers-style, shown above)** โ€” a short, runnable Julia code block illustrating typical usage with qualified paths. +- **Q&A (CTModels-style)** โ€” a list of "I want to ..." entries, each pointing to the relevant API page or guide. Useful when the API surface is wide. + +## Conventions + +### Admonitions + +| Type | Use | +| --- | --- | +| `!!! info` | Contrasting the package with siblings, scope statements | +| `!!! note` | Pointers to the root package or related material | +| `!!! warning` | Qualified-access policy, breaking caveats | +| `!!! tip` | Performance hints, idiomatic patterns | + +### Cross-references + +- `[Title](@ref)` โ€” in-package references (resolves to a heading or docstring in the current docs). +- `` [`Pkg.Submodule.sym`](@extref) `` โ€” references to symbols in a dependency with separate documentation. Requires the dependency to appear in `InterLinks`. + +See [`docstrings.md`](docstrings.md) for the full cross-reference policy. + +### Code examples + +Prefer fully qualified calls in examples (`CTFlows.Systems.dimensions(sys)`) โ€” consistent with [`modules.md`](modules.md). Exceptions: short snippets where the qualified form would obscure the point and the symbol is unambiguous. + +## Build Commands + +### Local build + +```bash +julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate(); include("docs/make.jl")' +``` + +### CI deployment + +Handled by `deploydocs(; repo=repo_url * ".git", devbranch="main")` at the bottom of `make.jl`. The standard control-toolbox GitHub Actions workflow takes care of the rest. + +## Quality Checklist + +Before finalising the documentation setup, verify: + +- [ ] `docs/make.jl` uses the `with_api_reference()` wrapper. +- [ ] `docs/api_reference.jl` exists with `generate_api_reference`, `with_api_reference`, and `_cleanup_pages` functions. +- [ ] `DocumenterReference` extension is loaded and reset via `reset_config!()`. +- [ ] If `[@extref]` is used in any docstring, `DocumenterInterLinks` is set up in `make.jl` with one `InterLinks` entry per cross-referenced dependency, and `links` is passed to `makedocs(; plugins=[links])`. +- [ ] One `automatic_reference_documentation` call per submodule, both `public=true` and `private=true`, with `external_modules_to_document=[CTFlows]` when relevant. +- [ ] Each known extension is detected via `Base.get_extension` and conditionally documented. +- [ ] `DocMeta.setdocmeta!` loop covers the package and its loaded extensions when doctests are used. +- [ ] `docs/src/index.md` contains: meta block, ecosystem link, info/note/warning admonitions, modules table, guide links via `[@ref]`, API reference note, and Quick Start. +- [ ] All cross-references use `[@ref]` / `[@extref]` correctly (see [`docstrings.md`](docstrings.md)). +- [ ] Hand-written guides are placed under `docs/src/guides/`. +- [ ] No hand-written API pages โ€” everything in `api/` is generated and cleaned up by `_cleanup_pages`. diff --git a/.windsurf/rules/exceptions.md b/.windsurf/rules/exceptions.md new file mode 100644 index 00000000..27a65a62 --- /dev/null +++ b/.windsurf/rules/exceptions.md @@ -0,0 +1,521 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Exception Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "โš ๏ธ **Applying Exception Rule**: [specific exception principle being applied]" + +This ensures transparency about which exception standard is being used and why. + +--- + +This document defines the exception handling standards for the Control Toolbox project. All error conditions must be handled using structured, informative exceptions that provide clear guidance to users. + +## Core Principles + +1. **Clear Messages**: Error messages must be immediately understandable +2. **Actionable Suggestions**: Provide guidance on how to fix the problem +3. **Rich Context**: Include what was expected, what was received, and where +4. **User-Friendly**: Format errors for end users, not just developers + +## Exception Types + +CTBase provides seven exception types, all subtypes of `CTException`. Import with: + +```julia +import CTBase.Exceptions +``` + +Catch all domain errors uniformly with: + +```julia +try + risky_operation() +catch e + if e isa Exceptions.CTException + handle_error(e) + else + rethrow() + end +end +``` + +**Hierarchy:** + +```text +CTException (abstract) +โ”œโ”€โ”€ IncorrectArgument # Invalid argument value +โ”œโ”€โ”€ PreconditionError # Wrong order / state violation +โ”œโ”€โ”€ NotImplemented # Interface stub +โ”œโ”€โ”€ ParsingError # DSL / syntax error +โ”œโ”€โ”€ AmbiguousDescription # Description tuple not found +โ”œโ”€โ”€ ExtensionError # Missing optional dependency +โ””โ”€โ”€ SolverFailure # Solver / integrator failure +``` + +--- + +### 1. IncorrectArgument + +Use when an individual argument is invalid or violates a precondition. + +**Fields:** + +- `msg::String`: Main error message (required) +- `got::Union{String, Nothing}`: What value was received (optional) +- `expected::Union{String, Nothing}`: What value was expected (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(IncorrectArgument("Invalid criterion")) + +# With got/expected +throw(IncorrectArgument( + "Invalid criterion", + got=":invalid", + expected=":min or :max" +)) + +# Full context +throw(IncorrectArgument( + "Invalid criterion", + got=":invalid", + expected=":min or :max", + suggestion="Use objective!(ocp, :min, ...) or objective!(ocp, :max, ...)", + context="objective! function" +)) +``` + +**When to use:** + +- Invalid function arguments +- Type mismatches +- Value out of range +- Missing required parameters +- Invalid combinations of parameters + +--- + +### 2. PreconditionError + +Use when a function call violates a precondition or is not allowed in the current state of the system. The arguments may be valid, but the *timing* or *state* is wrong. + +**Fields:** + +- `msg::String`: Main error message (required) +- `reason::Union{String, Nothing}`: Why the precondition failed (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(PreconditionError("State must be set before dynamics")) + +# With reason and suggestion +throw(PreconditionError( + "Cannot call state! twice", + reason="state has already been defined for this OCP", + suggestion="Create a new OCP instance" +)) + +# Full context +throw(PreconditionError( + "Cannot modify frozen OCP", + reason="OCP has been finalized and is immutable", + suggestion="Create a new OCP or modify before calling finalize!()", + context="constraint! function" +)) +``` + +**When to use:** + +- Functions called in the wrong order +- Operations on uninitialized objects +- State machine violations +- Workflow step dependencies + +**Distinction from `IncorrectArgument`:** + +- `IncorrectArgument`: the *value* of an argument is wrong +- `PreconditionError`: the *timing* or *state* is wrong + +--- + +### 3. NotImplemented + +Use to mark interface points that must be implemented by concrete subtypes. + +**Fields:** + +- `msg::String`: Description of what is not implemented (required) +- `required_method::Union{String, Nothing}`: Method signature that needs implementation (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(NotImplemented("solve! not implemented for MyStrategy")) + +# With required_method and suggestion +throw(NotImplemented( + "Method solve! not implemented", + required_method="solve!(::MyStrategy, ...)", + suggestion="Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" +)) + +# For abstract type contracts +abstract type AbstractStrategy end + +function solve!(strategy::AbstractStrategy, problem) + throw(NotImplemented( + "solve! must be implemented for each strategy type", + required_method="solve!(::$(typeof(strategy)), problem)", + suggestion="Define solve!(::$(typeof(strategy)), problem)", + context="strategy dispatch" + )) +end +``` + +**When to use:** + +- Abstract type interface methods +- Extension points +- Optional features not yet implemented +- Platform-specific functionality + +--- + +### 4. ParsingError + +Use for parsing errors in DSLs or structured input. + +**Fields:** + +- `msg::String`: Description of the parsing error (required) +- `location::Union{String, Nothing}`: Where in the input the error occurred (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) + +**Examples:** + +```julia +# Simple message +throw(ParsingError("Unexpected token 'end'")) + +# With location and suggestion +throw(ParsingError( + "Unexpected token 'end'", + location="line 42, column 15", + suggestion="Check syntax balance or remove extra 'end'" +)) +``` + +**When to use:** + +- DSL parsing errors +- Configuration file parsing +- Input validation during parsing +- Syntax errors + +--- + +### 5. AmbiguousDescription + +Use when a description (a tuple of `Symbol`s) cannot be matched to any known valid description in a catalogue. + +**Fields:** + +- `description::Tuple{Vararg{Symbol}}`: The ambiguous or incorrect description tuple (required) +- `candidates::Union{Vector{String}, Nothing}`: Suggested valid alternatives (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Constructor:** `AmbiguousDescription(description; msg=..., candidates=..., suggestion=..., context=...)` + +**Examples:** + +```julia +# Simple +throw(AmbiguousDescription((:f,))) + +# With candidates and suggestion +throw(AmbiguousDescription( + (:descent,), + candidates=["(:descent, :bfgs, :bisection)", "(:descent, :gradient, :fixedstep)"], + suggestion="Use a complete description like (:descent, :bfgs, :bisection)", + context="algorithm selection" +)) +``` + +**When to use:** + +- Description-based APIs where a partial `Symbol` tuple doesn't match any catalogue entry +- Algorithm selection via symbolic descriptions +- Pattern matching in mathematical modeling DSLs + +--- + +### 6. ExtensionError + +Use when a feature requires optional dependencies (weak dependencies) that are not loaded. + +**Fields:** + +- `weakdeps::Tuple{Vararg{Symbol}}`: Names of missing packages +- `feature::Union{String, Nothing}`: Which feature requires them (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Constructor:** `ExtensionError(pkgs::Symbol...; message="", feature=nothing, context=nothing)` + +โš ๏ธ `ExtensionError()` with no arguments throws `PreconditionError` instead. + +**Examples:** + +```julia +# Single missing dependency +throw(ExtensionError(:Plots)) + +# With feature description +throw(ExtensionError( + :Plots, + feature="result visualization", + context="plot_results function" +)) + +# Multiple missing dependencies +throw(ExtensionError(:SciMLBase, :OrdinaryDiffEq; + message="to integrate ODEs", + feature="ODE integration", + context="build_flow" +)) +``` + +**When to use:** + +- Extension stubs in `ext/` files (SciML, ForwardDiff, Plots, StaticArrays) +- Weak dependency not loaded by the user +- ODE integration, plotting, AD functionality behind extensions + +--- + +### 7. SolverFailure + +Use when a solver (ODE integrator, optimization solver, linear solver) fails to complete successfully. + +**Fields:** + +- `msg::String`: Error message describing the failure (required) +- `retcode::Union{String, Nothing}`: Solver-specific return code (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple +throw(SolverFailure("ODE integration failed")) + +# With SciML retcode +throw(SolverFailure( + "ODE integration failed", + retcode=":Unstable", + suggestion="Reduce time step or check initial conditions", + context="SciML integrator in build_flow" +)) + +# Optimization solver +throw(SolverFailure( + "Optimization solver did not converge", + retcode=":MaxIterations", + suggestion="Increase max iterations or adjust tolerance settings", + context="IPOPT solver in CTDirect" +)) +``` + +**Common SciML return codes:** `:Unstable`, `:DtLessThanMin`, `:MaxIters`, `:Success` + +**When to use:** + +- ODE integration failures in CTFlows (SciML integrators) +- Non-convergence of optimization solvers +- Ill-conditioned linear systems +- Any numerical solver returning a failure status + +**Distinction from other exceptions:** + +- `IncorrectArgument`: the *input* is invalid +- `PreconditionError`: the *state* or *timing* is wrong +- `SolverFailure`: the *numerical computation* itself failed + +--- + +## Quick Reference + +| Situation | Exception | +| --- | --- | +| Invalid argument value | `IncorrectArgument` | +| Wrong function call order / state | `PreconditionError` | +| Unimplemented interface method | `NotImplemented` | +| DSL / syntax parsing error | `ParsingError` | +| Description tuple not matched | `AmbiguousDescription` | +| Missing optional dependency | `ExtensionError` | +| Solver / integrator failure | `SolverFailure` | + +--- + +## Best Practices + +### Write Clear Messages + +**โœ… Good - Specific and clear:** + +```julia +throw(IncorrectArgument( + "State dimension must be positive", + got="n = -1", + expected="n > 0", + suggestion="Provide a positive integer for state dimension" +)) +``` + +**โŒ Bad - Vague:** + +```julia +throw(IncorrectArgument("Invalid input")) +``` + +### Use Appropriate Exception Types + +**โœ… Good - Correct type:** + +```julia +throw(IncorrectArgument("n must be positive", got="n = -1", expected="n > 0")) +throw(PreconditionError("Cannot modify frozen OCP", reason="OCP is immutable")) +throw(NotImplemented("solve! not implemented", required_method="solve!(::MyStrategy, ...)")) +``` + +**โŒ Bad - Wrong type:** + +```julia +throw(IncorrectArgument("OCP already finalized")) # Should be PreconditionError +throw(PreconditionError("n must be positive")) # Should be IncorrectArgument +``` + +--- + +## Common Patterns + +### Validation Pattern + +```julia +function validate_dimension(n::Int, name::String) + if n <= 0 + throw(IncorrectArgument( + "Dimension must be positive", + got="$name = $n", + expected="$name > 0", + suggestion="Provide a positive integer for $name" + )) + end +end +``` + +### State Machine Pattern + +```julia +mutable struct OCP + state_defined::Bool +end + +function state!(ocp::OCP, n::Int) + if ocp.state_defined + throw(PreconditionError( + "Cannot call state! twice", + reason="state has already been defined for this OCP", + suggestion="Create a new OCP instance" + )) + end + ocp.state_defined = true +end +``` + +### Interface Pattern + +```julia +abstract type AbstractStrategy end + +function solve!(strategy::AbstractStrategy, problem) + throw(NotImplemented( + "solve! must be implemented for each strategy type", + required_method="solve!(::$(typeof(strategy)), problem)", + suggestion="Define solve!(::$(typeof(strategy)), problem) or import the relevant package" + )) +end +``` + +--- + +## Testing Exceptions + +```julia +@testset "Exception Types" begin + @test_throws IncorrectArgument invalid_function(bad_arg) + + err = try + invalid_function(bad_arg) + catch e + e + end + @test err isa IncorrectArgument + @test occursin("Invalid criterion", err.msg) +end +``` + +--- + +## Quality Checklist + +Before finalizing exception handling, verify: + +- [ ] Exception type is appropriate (IncorrectArgument, PreconditionError, NotImplemented, ParsingError, AmbiguousDescription, ExtensionError, SolverFailure) +- [ ] Error message is clear and specific +- [ ] `got` and `expected` fields provided when applicable +- [ ] Actionable `suggestion` provided +- [ ] `context` provided for complex errors +- [ ] Exception is tested with `@test_throws` +- [ ] Error message is user-friendly (no jargon) +- [ ] Suggestion is concrete and actionable + +--- + +## Anti-Patterns + +```julia +# โŒ Generic errors +error("Something went wrong") + +# โŒ Missing context +throw(IncorrectArgument("Invalid value")) + +# โŒ No suggestions +throw(IncorrectArgument("Unknown constraint type", got=":boundary")) +``` + +--- + +## Related Rules + +- `.windsurf/rules/docstrings.md` - Document exceptions in `# Throws` section +- `.windsurf/rules/architecture.md` - Error handling architecture +- `.windsurf/rules/testing.md` - Exception testing patterns diff --git a/.windsurf/rules/modules.md b/.windsurf/rules/modules.md new file mode 100644 index 00000000..1bbd1278 --- /dev/null +++ b/.windsurf/rules/modules.md @@ -0,0 +1,350 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Submodule Architecture Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ—๏ธ **Applying Modules Rule**: [specific principle being applied]" + +This ensures transparency about which submodule-architecture principle is being used and why. + +--- + +This document defines the submodule organisation, import conventions, and qualification rules for Julia code in the Control Toolbox ecosystem. The reference implementation is [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl); every package in the stack (including CTFlows) must follow the same pattern so that code, tests, and cross-package imports remain predictable and stable. + +## Core Principles + +1. **One submodule per concern** โ€” each submodule lives in its own subdirectory `src//.jl` and has a single, well-defined responsibility. +2. **Manifest-only top-level** โ€” the package's top-level file only `include`s subdirectories and does `using .Submodule`; **it exports nothing**. +3. **Submodules export their public API** โ€” every submodule declares `export` for the symbols it considers public; internal helpers stay unexported and are reached via full qualification. +4. **Qualified imports for external packages** โ€” use `using PackageName: PackageName` or `import PackageName.SubModule` instead of bare `using`. +5. **Qualified usage everywhere** โ€” always call sibling-module or external symbols as `Submodule.function` / `Submodule.Type`; never rely on implicit scope. +6. **One-way dependency flow** โ€” submodules form a DAG; lower-level modules cannot import higher-level ones, and there are no cycles. + +## Submodule Directory Layout + +Each submodule occupies its own subdirectory. The `.jl` file at the root of that directory is the **manifest**; every other `.jl` file is `include`d by the manifest (possibly from nested subdirectories). + +Reference layout (CTSolvers): + +```text +src/ +โ”œโ”€โ”€ CTSolvers.jl # top-level manifest (exports nothing) +โ”œโ”€โ”€ Core/ +โ”‚ โ””โ”€โ”€ Core.jl # Core manifest +โ”œโ”€โ”€ Options/ +โ”‚ โ”œโ”€โ”€ Options.jl # manifest +โ”‚ โ”œโ”€โ”€ not_provided.jl +โ”‚ โ”œโ”€โ”€ option_value.jl +โ”‚ โ”œโ”€โ”€ option_definition.jl +โ”‚ โ””โ”€โ”€ extraction.jl +โ”œโ”€โ”€ Strategies/ +โ”‚ โ”œโ”€โ”€ Strategies.jl # manifest +โ”‚ โ”œโ”€โ”€ display_formatting.jl +โ”‚ โ”œโ”€โ”€ contract/ # nested subdirectory +โ”‚ โ”‚ โ”œโ”€โ”€ abstract_strategy.jl +โ”‚ โ”‚ โ”œโ”€โ”€ metadata.jl +โ”‚ โ”‚ โ””โ”€โ”€ strategy_options.jl +โ”‚ โ””โ”€โ”€ api/ +โ”‚ โ”œโ”€โ”€ registry.jl +โ”‚ โ”œโ”€โ”€ builders.jl +โ”‚ โ””โ”€โ”€ โ€ฆ +โ”œโ”€โ”€ Optimization/ +โ”œโ”€โ”€ Orchestration/ +โ”œโ”€โ”€ Modelers/ +โ”œโ”€โ”€ DOCP/ +โ””โ”€โ”€ Solvers/ +``` + +Rules: + +- A submodule directory contains exactly one manifest named after the module. +- Nested subdirectories (`contract/`, `api/`, โ€ฆ) are allowed for organisation. +- No logic in the manifest โ€” only imports, includes, and exports. + +## The Submodule Manifest Pattern + +Canonical structure of `src//.jl`: + +```julia +""" +Module docstring โ€” purpose, responsibilities, dependencies. +""" +module Name + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using SolverCore: SolverCore +using ADNLPModels: ADNLPModels + +# 2. Internal sibling-submodule imports +using ..Options +using ..Strategies +import ..Core as CTCore +using ..Core: AbstractTag + +# 3. Include files (ordered by internal dependency) +include(joinpath(@__DIR__, "abstract_types.jl")) +include(joinpath(@__DIR__, "contract.jl")) +include(joinpath(@__DIR__, "builders.jl")) + +# 4. Public API โ€” exports only +export AbstractX, ConcreteX +export build_x, validate_x + +end # module Name +``` + +Ordering rules: + +1. Docstring first (module-level documentation). +2. `module` declaration. +3. External-package imports. +4. Internal sibling imports. +5. `include(...)` calls (in dependency order). +6. `export` statements. +7. `end # module Name`. + +Section separators (`# ===โ€ฆ===`) are encouraged for readability. + +## External Package Import Style + +Three acceptable patterns, in order of preference: + +### 1. Name-qualified `using` (preferred for packages) + +```julia +using SolverCore: SolverCore +using ADNLPModels: ADNLPModels +``` + +This brings only the module name into scope. Call sites use `SolverCore.solve(...)`, `ADNLPModels.ADNLPModel(...)`. + +### 2. Submodule `import` + +```julia +import CTBase.Exceptions +``` + +This makes `Exceptions` available as a qualifier but does not bring its exported symbols into scope. Call sites use `Exceptions.NotImplemented(...)`, `Exceptions.IncorrectArgument(...)`. + +### 3. Symbol-qualified `import` (reserved for macros and heavily-used single symbols) + +```julia +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +``` + +Use only for: + +- Macros (cannot be qualified at usage site without awkwardness). +- Single symbols used pervasively where qualification would add noise (rare). + +### Forbidden patterns + +```julia +# โŒ Bare using โ€” pollutes namespace +using ADNLPModels + +# โŒ Symbol-list using โ€” opaque origin at call sites +using CTBase: AbstractModel, Dimension, validate, check +``` + +## Internal Submodule Import Style + +From within a submodule manifest, sibling submodules are reached via the parent scope `..`. + +### Importing a whole sibling + +```julia +using ..Options +using ..Strategies +``` + +Brings the submodule name into scope. Call sites use `Options.extract_option(...)`, `Strategies.metadata(...)`. + +### Aliasing + +```julia +import ..Core as CTCore +``` + +Use when the original name would conflict with another symbol, or when a shorter internal handle improves readability. + +### Specific symbol from a sibling + +```julia +using ..Core: AbstractTag +``` + +Use only when the symbol is pervasive within the current submodule *and* using it unqualified is unambiguous (typically for abstract types inherited from a Core module). + +## Qualification at Call Sites + +All references to external or sibling-submodule symbols must be qualified. This is the main consequence of the import conventions above. + +**โœ… Correct:** + +```julia +function Strategies.metadata(::Type{<:Modelers.ADNLP{P}}) where {P<:CPU} + return Strategies.StrategyMetadata( + Strategies.OptionDefinition( + name=:backend, type=Symbol, default=:optimized, + description="AD backend used by ADNLPModels", + ), + ) +end + +throw( + Exceptions.NotImplemented( + "Model building not implemented"; + required_method = "(modeler::$(typeof(modeler)))(prob::Optimization.AbstractOptimizationProblem, initial_guess)", + suggestion = "Implement the callable method for $(typeof(modeler)) to build NLP models", + context = "AbstractNLPModeler - required method implementation", + ), +) +``` + +**โŒ Wrong โ€” unqualified, fragile to refactoring:** + +```julia +# Where does StrategyMetadata come from? ADNLP? CPU? +function metadata(::Type{<:ADNLP{P}}) where {P<:CPU} + return StrategyMetadata( + OptionDefinition(name=:backend, type=Symbol, default=:optimized), + ) +end +``` + +### Why qualification matters + +- **Explicit origin at every call site** โ€” readers immediately see which submodule a symbol belongs to. +- **Refactor safety** โ€” if `Strategies` is renamed or moved, only the `using ..Strategies` line changes; all call sites stay correct as long as the new import uses the same name (or is aliased to it). +- **No accidental shadowing** โ€” qualified names cannot be captured by a local variable with the same stem. +- **Cross-package consistency** โ€” the same pattern works for external packages (`Exceptions.NotImplemented`) and sibling submodules (`Strategies.metadata`). + +## Dependency Order and the DAG + +The loading order in the top-level manifest must reflect a correct topological order of submodule dependencies. + +Reference DAG (CTSolvers): + +```text +Core + โ”œโ”€โ”€ Options + โ”‚ โ””โ”€โ”€ Strategies + โ”‚ โ”œโ”€โ”€ Orchestration + โ”‚ โ””โ”€โ”€ Optimization + โ”‚ โ”œโ”€โ”€ Modelers + โ”‚ โ”‚ โ””โ”€โ”€ DOCP + โ”‚ โ”‚ โ””โ”€โ”€ Solvers +``` + +Rules: + +- The top-level manifest lists `include`/`using` calls in topological order. +- A submodule can only `using ..Lower` where `Lower` was already loaded. +- **No cycles** โ€” if two submodules need each other, extract the shared concern into a lower-level submodule (typically `Core` or a dedicated `Types` module). + +## Exports and Public API + +Two-level rule: + +- **Submodule level** โ€” each submodule declares `export` at the end of its manifest for the symbols that form its public API. Internal helpers (names prefixed with `_`, or kept unexported by convention) stay unexported and are reached via full qualification. +- **Top-level (package) level** โ€” the package manifest **exports nothing**. It only loads submodules with `using .Submodule` so they become accessible as `Package.Submodule`. There are **no `export` statements** at the top level. + +### Consequences + +- Users access public symbols via `Package.Submodule.sym` โ€” explicit, stable, self-documenting. +- Adding a new public symbol in a submodule is a local change (one `export` line). +- Moving a symbol between submodules is visible at the call site (the qualification changes). +- Namespace conflicts between submodules cannot occur at package load time, because nothing is brought into the package-level scope. + +### Top-level manifest example + +```julia +""" + CTFlows + +Brief description of the package. + +# Architecture Overview + +CTFlows is organised into specialised submodules; all public symbols are +accessed via qualified paths (e.g. `CTFlows.Systems.AbstractSystem`). +""" +module CTFlows + +include(joinpath(@__DIR__, "Core", "Core.jl")) +using .Core + +include(joinpath(@__DIR__, "Systems", "Systems.jl")) +using .Systems + +include(joinpath(@__DIR__, "Modelers", "Modelers.jl")) +using .Modelers + +# โ€ฆ more submodules โ€ฆ + +# NO export statements here. + +end # module CTFlows +``` + +### User access patterns + +```julia +using CTFlows # brings no symbols into scope directly +CTFlows.Systems.AbstractSystem # fully qualified (recommended) + +using CTFlows.Systems # brings Systems exports into scope +AbstractSystem # unqualified (user's choice, at their own risk) +``` + +The `export` inside each submodule makes the unqualified form available to users who explicitly opt in via `using CTFlows.Submodule`. The package-level `using CTFlows` remains silent. + +## Proposed CTFlows Layout + +Informed by [`reports/design.md`](../../reports/design.md), the CTFlows submodule breakdown mirrors CTSolvers' separation of concerns: + +```text +src/ +โ”œโ”€โ”€ CTFlows.jl # top-level manifest, exports nothing +โ”œโ”€โ”€ Core/Core.jl # shared types and utilities +โ”œโ”€โ”€ Systems/Systems.jl # AbstractSystem + concrete systems + MultiPhaseSystem +โ”œโ”€โ”€ Flows/Flows.jl # AbstractFlow, Flow, MultiPhaseFlow +โ”œโ”€โ”€ Modelers/Modelers.jl # AbstractFlowModeler + concrete modelers +โ”œโ”€โ”€ Integrators/Integrators.jl # AbstractODEIntegrator + concrete integrators +โ”œโ”€โ”€ ADBackends/ADBackends.jl # AbstractADBackend + concrete backends +โ””โ”€โ”€ Pipelines/Pipelines.jl # build_system, build_flow, integrate, build_solution, solve +``` + +Dependency order (topologically sorted): + +```text +Core + โ”œโ”€โ”€ Systems + โ”œโ”€โ”€ Integrators + โ”œโ”€โ”€ ADBackends + โ”œโ”€โ”€ Modelers (depends on Systems, ADBackends) + โ”œโ”€โ”€ Flows (depends on Systems, Integrators) + โ””โ”€โ”€ Pipelines (depends on all of the above) +``` + +The `Options` and `Strategies` infrastructure is consumed from CTSolvers via standard package imports (`using CTSolvers: CTSolvers` then qualified calls like `CTSolvers.Strategies.AbstractStrategy`). + +## Quality Checklist + +Before finalising a submodule or a package restructure, verify: + +- [ ] Each submodule lives in its own subdirectory with a `.jl` manifest. +- [ ] The manifest contains only a docstring, `module`, imports, `include`s, `export`s, and `end`. +- [ ] External-package imports use `using Pkg: Pkg`, `import Pkg.Sub`, or (for macros) `import Pkg: sym`. +- [ ] Internal imports use `using ..Sibling`, `import ..Sibling as Alias`, or `using ..Sibling: Sym`. +- [ ] All references to sibling or external symbols are fully qualified at call sites. +- [ ] The dependency graph is acyclic and respected by the top-level loading order. +- [ ] Each submodule declares `export` for its public API. +- [ ] The top-level package manifest contains **no** `export` statements. diff --git a/.windsurf/rules/performance.md b/.windsurf/rules/performance.md new file mode 100644 index 00000000..2509ab12 --- /dev/null +++ b/.windsurf/rules/performance.md @@ -0,0 +1,304 @@ +--- +trigger: model_decision +--- + +# Julia Performance and Type Stability Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "โšก **Applying Performance Rule**: [specific performance principle being applied]" + +This ensures transparency about which performance standard is being used and why. + +--- + +This document defines performance and type stability standards for the Control Toolbox project. Performance-critical code must follow these guidelines to ensure optimal execution speed and memory efficiency. + +## Core Principles + +1. **Measure First**: Profile before optimizing +2. **Focus on Hot Paths**: Optimize where it matters (inner loops, critical functions) +3. **Type Stability**: Ensure type-stable code (see `type-stability` rule) +4. **Avoid Premature Optimization**: Optimize only when necessary +5. **Maintain Readability**: Don't sacrifice clarity for marginal gains + +## Performance Hierarchy + +### Critical (Must Optimize) + +- Inner loops (called millions of times) +- Numerical computations in solvers +- Hot paths identified by profiling +- Real-time systems + +### Important (Should Optimize) + +- Frequently called functions +- Data processing pipelines +- API functions with performance requirements + +### Low Priority (Optimize if Easy) + +- One-time setup code +- User-facing convenience functions +- Error handling paths +- Debugging utilities + +## Profiling + +### Using Profile.jl + +```julia +using Profile + +@profile my_function(args...) +Profile.print() +Profile.clear() +@profile (for i in 1:1000; my_function(args...); end) +``` + +### Using ProfileView.jl + +```julia +using ProfileView + +@profview my_function(args...) +@profview for i in 1:1000 + my_function(args...) +end +``` + +**Interpreting Results:** +- **Red bars**: Hot spots (most time spent) +- **Wide bars**: Functions called many times +- **Type instabilities**: Yellow/red warnings + +## Benchmarking + +### Using BenchmarkTools.jl + +```julia +using BenchmarkTools + +@benchmark my_function($args...) + +b1 = @benchmark old_implementation($args...) +b2 = @benchmark new_implementation($args...) +judge(median(b2), median(b1)) +``` + +**Best Practices:** + +```julia +# โœ… Interpolate variables (avoids global variable penalty) +x = rand(1000) +@benchmark my_function($x) + +# โœ… Warm up before benchmarking +my_function(args...) +@benchmark my_function($args...) +``` + +## Memory Allocations + +### Reducing Allocations + +**โœ… Good - Preallocate buffers:** + +```julia +function process_data!(output, input) + for i in eachindex(input) + output[i] = input[i]^2 + end + return output +end + +output = similar(input) +process_data!(output, input) # No allocations +``` + +**โŒ Bad - Allocate in loop:** + +```julia +function process_data(input) + output = [] + for x in input + push!(output, x^2) # Allocates each iteration + end + return output +end +``` + +**โœ… Good - Use views instead of copies:** + +```julia +sub = @view matrix[1:10, :] # No allocation +``` + +**โœ… Good - In-place operations:** + +```julia +A .= B .+ C # In-place, no allocation +``` + +## Common Optimizations + +### 1. Avoid Global Variables + +```julia +# โŒ Bad +global_counter = 0 + +# โœ… Good +const COUNTER = Ref(0) +function increment() + COUNTER[] += 1 +end +``` + +### 2. Use @inbounds for Bounds-Checked Loops + +```julia +function sum_array(arr) + s = zero(eltype(arr)) + @inbounds for i in eachindex(arr) + s += arr[i] + end + return s +end +``` + +**โš ๏ธ Warning:** `@inbounds` disables bounds checking. Use only when safe. + +### 3. Use @simd for Vectorization + +```julia +function sum_array(arr) + s = zero(eltype(arr)) + @simd for i in eachindex(arr) + s += arr[i] + end + return s +end +``` + +### 4. Use StaticArrays for Small Arrays + +```julia +using StaticArrays + +v = SVector(1.0, 2.0, 3.0) +m = SMatrix{3,3}(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) +result = m * v # No allocation! +``` + +### 5. Avoid Untyped Containers + +```julia +# โŒ Bad +results = [] # Vector{Any} + +# โœ… Good +results = Float64[] +``` + +### 6. Use Multiple Dispatch Effectively + +```julia +function process(x) # Generic fallback +end + +function process(x::Float64) # Fast specialized method +end +``` + +## Performance Testing + +### Allocation Tests + +```julia +@testset "Allocations" begin + x = rand(1000) + allocs = @allocated process!(x) + @test allocs == 0 + + allocs = @allocated build_model(x) + @test allocs < 1000 # bytes +end +``` + +### Benchmark Tests + +```julia +@testset "Performance" begin + x = rand(1000) + b = @benchmark process($x) + @test median(b.times) < 1_000_000 # < 1ms + @test b.allocs == 0 +end +``` + +## Optimization Workflow + +1. **Profile** โ€” `@profview my_application()` +2. **Measure baseline** โ€” `baseline = @benchmark critical_function($args...)` +3. **Optimize** โ€” fix type instabilities, reduce allocations, use specialized algorithms +4. **Measure improvement** โ€” compare `median(baseline.times)` vs `median(optimized.times)` +5. **Verify correctness** โ€” `@test optimized_function(args...) โ‰ˆ baseline_function(args...)` + +## When NOT to Optimize + +**โŒ Don't optimize:** +- Before profiling +- Code that runs once +- Code that's already fast enough +- At the expense of readability + +## Parallelization + +### Using Threads + +```julia +using Base.Threads + +function parallel_sum(arr) + sums = zeros(nthreads()) + @threads for i in eachindex(arr) + sums[threadid()] += arr[i] + end + return sum(sums) +end +``` + +**Good candidates for parallelization:** independent computations, large data sets, CPU-bound tasks. + +## Quality Checklist + +Before finalizing performance optimizations: + +- [ ] Profiled to identify bottlenecks +- [ ] Benchmarked baseline performance +- [ ] Optimized critical paths only +- [ ] Verified type stability with `@inferred` +- [ ] Tested allocations are acceptable +- [ ] Verified correctness after optimization +- [ ] Maintained code readability +- [ ] Measured actual improvement + +## Tools Reference + +| Tool | Purpose | +|---|---| +| `Profile.jl` | Built-in profiling | +| `ProfileView.jl` | Visual profiling | +| `BenchmarkTools.jl` | Precise benchmarking | +| `@time` | Quick timing | +| `@allocated` | Allocation tracking | +| `@code_warntype` | Type stability | +| `StaticArrays.jl` | Fast small arrays | + +## Related Skills + +- `type-stability` rule โ€” type stability standards (critical for performance) +- `testing-creation` rule โ€” performance testing patterns +- `architecture` rule โ€” architecture patterns that affect performance diff --git a/.windsurf/rules/plan.md b/.windsurf/rules/plan.md new file mode 100644 index 00000000..2f9c3350 --- /dev/null +++ b/.windsurf/rules/plan.md @@ -0,0 +1,308 @@ +--- +trigger: model_decision +--- + +# Julia Implementation Plan Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“‹ **Applying Plan Rule**: [specific planning principle being applied]" + +This ensures transparency about which planning standard is being used and why. + +--- + +This document defines how to produce a complete, actionable implementation plan before writing any code. Plans are written first; implementation follows the plan step by step. **Docstrings are never written during implementation โ€” they are a dedicated final step.** + +## Core Principles + +1. **Plan before code** โ€” a plan must be fully written and reviewed before any file is touched. +2. **One step = one atomic change** โ€” each step modifies a single file or a single cohesive concern. Steps must be independently reviewable. +3. **Dependency-aware ordering** โ€” steps must respect the DAG of submodule dependencies. A file that depends on another always comes after it. +4. **Interleaved test checkpoints** โ€” instead of a massive test phase at the end, plans must include regular test checkpoints after logical groups of implementation steps. This provides validation before moving to the next phase. +5. **Docstrings are last** โ€” no docstring is written during implementation steps. A single dedicated step at the very end of the plan writes all docstrings at once, when the code is stable. +6. **No silent assumptions** โ€” any architectural decision made during planning must be stated explicitly in the plan (load-order changes, new exports, removed symbols, etc.). +7. **Rules are cited at the point of use** โ€” every step that triggers a rule from `.windsurf/rules/` must name that rule explicitly so the implementer knows which standard to follow. + +## CTFlows Project Configuration + +### Package + +- **Name**: `CTFlows` +- **Base branch**: `develop` +- **Test entry point**: `test/runtests.jl` +- **Test subdirectory**: `test/suite//test_.jl` +- **Extension directory**: `ext/` + +### Module Dependency DAG + +```text +Common + โ†“ +Data + โ†“ +Systems + โ†“ +Integrators + โ†“ +Flows + โ†“ +Solutions + +``` + +### Submodule List + +| Submodule | Source file | Purpose | +| --- | --- | --- | +| `Common` | `src/Common/Common.jl` | AbstractTag, AbstractTrait, traits, ODE params | +| `Data` | `src/Data/Data.jl` | VectorField, HamiltonianVectorField | +| `Systems` | `src/Systems/Systems.jl` | AbstractSystem subtypes | +| `Integrators` | `src/Integrators/Integrators.jl` | AbstractIntegrator, result types | +| `Flows` | `src/Flows/Flows.jl` | AbstractFlow, Flow, MultiPhaseFlow | +| `Solutions` | `src/Solutions/Solutions.jl` | Solution building and accessors | + +### Test Subdirectory Map + +| Module(s) under test | Test subdirectory | +| --- | --- | +| Common | `suite/common/` | +| Data | `suite/data/` | +| Systems | `suite/systems/` | +| Integrators | `suite/integrators/` | +| Flows | `suite/flows/` | +| Solutions | `suite/solutions/` | + +## Rules Reference + +The following rules from `.windsurf/rules/` govern implementation. The plan must mention each rule **at the step where it applies**, not just once globally. + +| Rule | Scope | Typical trigger in a plan | +| --- | --- | --- | +| `architecture` | SOLID principles, patterns, module organisation | Any step that introduces a new type, new dependency, or restructures a module | +| `modules` (rule) | Submodule manifests, import style, qualification, export declarations | Any step touching a `.jl` manifest, import list, or `export` block | +| `exceptions` (rule) | Structured exceptions: `IncorrectArgument`, `PreconditionError`, `NotImplemented`, `ParsingError`, `AmbiguousDescription`, `ExtensionError`, `SolverFailure` | Any step adding a stub, a contract method, or an error path | +| `testing-creation` (rule) | Test structure, fake types at top-level, unit/integration/contract/error separation | Every test step | +| `testing-execution` (rule) | Running tests, coverage reports | The verification step | +| `type-stability` (rule) | `@inferred`, parametric types, avoiding `Any` | Steps introducing new structs or performance-critical functions | +| `performance` (rule) | Profiling, benchmarking, allocation reduction | Steps on hot paths or after type-stability work | +| `docstrings` (rule) | Docstring templates, `$(TYPEDEF)`, `$(TYPEDSIGNATURES)`, cross-references | The dedicated docstring step only | +| `documentation` (rule) | `docs/` organisation, `make.jl`, API reference generation | If the plan includes a documentation update step | + +## Plan Structure + +A valid plan contains the following sections **in order**: + +### 1. Title and summary + +```markdown +# + +<One-paragraph summary: what changes, why it changes, and what the end state looks like.> +``` + +### 2. What changes and why + +Describe: + +- **What is being changed**: files, symbols, types, interfaces. +- **Why**: the architectural motivation (decoupling, new contract, load-order fix, etc.). +- **What disappears**: explicitly list every symbol, callable, or file that is deleted. +- **What is added**: new files, new types, new exports. + +### 3. Dependency graph after the change + +Always include a dependency diagram showing the new module/file relationships: + +```text +Module A โ†’ Module B, Module C +Module B โ†’ Module D +``` + +Use the same notation as the existing codebase. This graph drives step ordering in section 5. + +### 4. Branch step + +Always start with: + +```markdown +### Step 0 โ€” Branch + +\`\`\`bash +git checkout develop && git pull +git checkout -b <branch-name> +\`\`\` +``` + +**Note:** Use `develop` as the base branch for CTFlows. + +### 5. Implementation steps + +Number every step starting from 1. Each step must follow this template: + +```markdown +### Step N โ€” `path/to/file.jl` [(new file) | (modified)] + +> ๐Ÿ“ Follow `architecture` rule โ€” [specific principle, e.g. "new abstract type follows the contract pattern"] +> ๐Ÿ—๏ธ Follow `modules` rule โ€” [specific rule, e.g. "add export at manifest end; use `using ..Sibling` for imports"] +> โš ๏ธ Follow `exceptions` rule โ€” [if stubs or error paths are added, e.g. "use `NotImplemented` with `required_method` and `suggestion` fields"] +> ๐Ÿ”ฌ Follow `type-stability` rule โ€” [if new parametric types or performance-critical functions are introduced] + +- <Bullet describing the first change in this file, naming exact symbols> +- <Bullet describing the second change> +- โ€ฆ + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched; + new stubs get a single `# TODO: docstring` comment only. + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched; +> new stubs get a single `# TODO: docstring` comment only. +``` + +Rules for step content: + +- **One file per step** when the change is non-trivial. Multiple small related files (e.g. a manifest + one tiny helper) may share a step if they are always changed together. +- **Name the exact symbols** being added, removed, or renamed โ€” no vague language like "update the code". +- **Specify signatures** for new functions: `function build_problem(int::AbstractIntegrator, sys, config; variable)`. +- **Specify struct fields** for new types: `struct SciMLIntegrationResult{S<:SciMLBase.AbstractODESolution}`, field `sol::S`. +- **Call out load-order changes** explicitly when a `using` or `include` order changes in a manifest. +- **Call out export changes**: which symbols are added to or removed from `export`. + +### 6. Test checkpoints (Interleaved) + +After a logical group of implementation steps, add a dedicated test checkpoint. Continue numbering. Plans should have multiple test checkpoints interspersed rather than one big block at the end. + +```markdown +### Step N โ€” Test Checkpoint: <Subsystem/Phase> + +> ๐Ÿงช Follow `testing-creation` rule โ€” [specific rule, e.g. "define all fake structs at module top-level; separate unit/integration/contract/error testsets"] +> ๐Ÿ”ฌ Follow `type-stability` rule โ€” [if type-stability tests are needed for new symbols] +> โ–ถ๏ธ Follow `testing-execution` rule โ€” run targeted tests for this phase. + +- Define `struct Fake<X> <: <AbstractType>` at module top-level in `test/suite/<subdir>/test_<name>.jl` (never inside test functions). +- Implement the required contract methods on the fake. +- Test sections: + - `@testset "Contract: NotImplemented errors"` โ€” verify stubs throw correctly. + - `@testset "Functional: <describe>"` โ€” verify behaviour with fakes. + - `@testset "Exports"` โ€” verify new exports are present, deleted symbols are gone. + - `@testset "Type Stability"` โ€” `@inferred` checks on new performance-critical functions (if applicable). +- Run targeted tests: + \`\`\`bash + julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/<subdir>"])' 2>&1 | tee /tmp/phase_N.log + \`\`\` +``` + +Rules for test steps: + +- Every new public symbol gets at least one test. +- Every deleted public symbol gets a regression test confirming it no longer exists. +- Fake types are defined at **module top-level**, never inside test functions. +- Test files are named `test_<name>.jl` and placed in the appropriate `test/suite/<subdir>/` directory. +- Use the test subdirectory map to determine where tests belong. + +### 7. Docstring step + +The final step (or final steps if large) writes all docstrings at once: + +```markdown +### Step N โ€” Docstrings + +> ๐Ÿ“š Follow `docstrings` rule โ€” use templates with `$(TYPEDEF)` and `$(TYPEDSIGNATURES)`, include required sections, use CTFlows-qualified references. + +- Add docstrings to all newly added public symbols in this phase. +- Replace `# TODO: docstring` comments with proper docstrings. +- Follow the CTFlows docstring conventions (full module paths like `CTFlows.Flows.build_flow`). +- Ensure cross-references use `[@ref]` for internal symbols and `[@extref]` for external symbols. +``` + +### 8. Verification step + +The final step runs the full test suite and checks coverage: + +```markdown +### Step N โ€” Verification + +> โ–ถ๏ธ Follow `testing-execution` rule โ€” run full test suite with coverage. + +- Run full test suite: + \`\`\`bash + julia --project -e 'using Pkg; Pkg.test("CTFlows")' 2>&1 | tee /tmp/verification.log + \`\`\` +- Check coverage if applicable. +- Review test output for failures. +``` + +### 9. Files summary + +List all files touched by the plan: + +```markdown +## Files Summary + +### New files +- `src/Flows/NewType.jl` +- `test/suite/flows/test_new_type.jl` + +### Modified files +- `src/Flows/Flows.jl` +- `src/Flows/building.jl` + +### Deleted files +- `src/Flows/OldType.jl` +``` + +## Naming Conventions + +### Branch names + +Use descriptive branch names: + +```bash +git checkout -b feature/add-hamiltonian-flows +git checkout -b refactor/extract-integrator-interface +git checkout -b fix/ode-integration-stability +``` + +### Test file names + +Test files are named `test_<name>.jl` where `<name>` describes what is being tested: + +```julia +test/suite/flows/test_flow.jl +test/suite/integrators/test_sciml_integrator.jl +test/suite/data/test_vector_field.jl +``` + +## Ordering Rules + +1. **Respect the module DAG**: steps touching lower-level modules (Common, Data) come before steps touching higher-level modules (Flows, Solutions). +2. **Manifests last within a module**: when editing a submodule, edit implementation files first, then update the `<Name>.jl` manifest at the end of that module's steps. +3. **Extensions after core**: any step touching `ext/` comes after all core steps are complete. +4. **Tests after implementation**: test checkpoints come after the implementation steps they validate. + +## Checklist + +Before finalizing a plan, verify: + +- [ ] Plan includes a clear title and one-paragraph summary +- [ ] "What changes and why" section is complete +- [ ] Dependency graph shows the new relationships +- [ ] Branch step uses `develop` as base +- [ ] Implementation steps are numbered starting from 1 +- [ ] Each step cites relevant rules at the point of use +- [ ] Steps respect the module DAG ordering +- [ ] Test checkpoints are interleaved (not just one at the end) +- [ ] Fake types are defined at module top-level in test steps +- [ ] Docstring step is separate and comes after all implementation +- [ ] Verification step runs the full test suite +- [ ] Files summary lists all new, modified, and deleted files +- [ ] No docstrings are written during implementation steps +- [ ] CTFlows-specific paths and names are used throughout + +## Related Rules + +- `.windsurf/rules/architecture.md` - Architecture and design principles +- `.windsurf/rules/modules.md` - Submodule conventions +- `.windsurf/rules/exceptions.md` - Exception handling standards +- `.windsurf/rules/docstrings.md` - Documentation standards +- `.windsurf/rules/testing-execution.md` - Test execution standards diff --git a/.windsurf/rules/testing-creation.md b/.windsurf/rules/testing-creation.md new file mode 100644 index 00000000..4fba115f --- /dev/null +++ b/.windsurf/rules/testing-creation.md @@ -0,0 +1,721 @@ +--- +trigger: glob +glob: "test/**/*.jl" +--- + +# Julia Testing Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿงช **Applying Testing Rule**: [specific testing principle being applied]" + +This ensures transparency about which testing standard is being used and why. + +--- + +This document defines the testing standards for the CTFlows.jl project. All Julia code modifications must be accompanied by appropriate tests following these guidelines. + +## Core Principles + +1. **Contract-First Testing**: Define and test contracts (interfaces) first using stubs/mocks to verify correct routing and behavior. Test both public APIs and internal functions when they implement important logic. +2. **Orthogonality**: Tests are independent from source code structure (test organization โ‰  src organization) +3. **Isolation**: Unit tests use mocks/fakes to isolate components; integration tests verify interactions +4. **Determinism**: Tests must be reproducible and not depend on external state +5. **Clarity**: Test intent must be immediately obvious from test names and structure + +## CTFlows Project Configuration + +### Package + +```julia +CTFlows +``` + +### Submodules + +| Submodule | Content | +| --- | --- | +| `Common` | `AbstractTag`, `AbstractTrait`, configs, traits, ODE parameters | +| `Data` | `AbstractVectorField`, `HamiltonianVectorField`, `VectorField` | +| `Systems` | `AbstractSystem` subtypes | +| `Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow`, building, calling | +| `Integrators` | `AbstractIntegrator`, `AbstractIntegrationResult`, building | +| `Solutions` | Solution building and accessors | + +### Import Style + +Use the `import X: X` qualified form so the submodule name is in scope: + +```julia +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Data: Data +import CTFlows.Systems: Systems +import CTFlows.Flows: Flows +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions +import CTSolvers.Strategies: Strategies +import CTSolvers.Options: Options +``` + +For extension test files that load SciML or StaticArrays, also add: + +```julia +using SciMLBase: SciMLBase, ODEProblem +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +import StaticArrays: SA +``` + +### Test Directory Structure + +```text +test/suite/ +โ”œโ”€โ”€ common/ # AbstractTag, AbstractTrait, configs, traits, ODE parameters +โ”œโ”€โ”€ data/ # AbstractVectorField, HamiltonianVectorField, VectorField +โ”œโ”€โ”€ flows/ # AbstractFlow, Flow, building, calling, callables +โ”œโ”€โ”€ integrators/ # AbstractIntegrator, building, SciML, IntegrationResult +โ”œโ”€โ”€ extensions/ # SciML, ForwardDiff, Plots, StaticArrays +โ”œโ”€โ”€ multiphase/ # MultiPhase flow tests (concatenation, calling) +โ”œโ”€โ”€ solutions/ # Solution building +โ”œโ”€โ”€ systems/ # AbstractSystem subtypes +โ””โ”€โ”€ meta/ # Aqua.jl quality checks +``` + +### Test Constants + +Every test file defines these constants at module level: + +```julia +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +``` + +### Test Entry Point Pattern + +```julia +# Inside the module +function test_<name>() + Test.@testset "<Description>" verbose=VERBOSE showtiming=SHOWTIMING begin + # โ€ฆ + end +end + +# CRITICAL: redefine in outer scope so the test runner can call it +test_<name>() = Test<Name>.test_<name>() +``` + +### Extension Access Pattern (for extension test files) + +```julia +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) +``` + +## Test Organization + +### Directory Structure + +Tests are organized under `test/suite/` by **functionality**, not by source file structure: + +- `suite/common/`: Common types tests (AbstractTag, AbstractTrait, configs, traits, ODE parameters) +- `suite/data/`: Data types tests (AbstractVectorField, HamiltonianVectorField, VectorField) +- `suite/flows/`: Flow types tests (AbstractFlow, Flow, building, calling, callables) +- `suite/integrators/`: Integrator tests (AbstractIntegrator, building, SciML, IntegrationResult) +- `suite/extensions/`: Extension tests (SciML, ForwardDiff, Plots, StaticArrays) +- `suite/multiphase/`: MultiPhase flow tests (concatenation, calling) +- `suite/solutions/`: Solution building tests +- `suite/systems/`: System types tests (AbstractSystem subtypes) +- `suite/meta/`: Meta tests (Aqua.jl quality checks) + +### File and Function Naming + +**Required pattern:** + +- File name: `test_<name>.jl` +- Entry function: `test_<name>()` (matching the filename exactly) + +**Example:** + +```julia +# File: test/suite/flows/test_abstract_flow.jl +module TestAbstractFlow + +import Test +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Data +import CTSolvers: CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_abstract_flow() + Test.@testset "Abstract Flow Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + # Tests here + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_flow() = TestAbstractFlow.test_abstract_flow() +``` + +## Test Categories + +**Vocabulary used in this document:** + +- **Fake** โ€” minimal struct that implements the required contract methods; used to isolate the component under test. +- **Stub** โ€” default method on an abstract type that throws `NotImplemented` or `ExtensionError`; tested by calling it on a fake type. +- **Mock** (interaction-recording) โ€” not used in CTFlows; fakes are sufficient. + +### 1. Unit Tests + +**Purpose**: Test single functions/components in isolation. + +**Characteristics:** + +- Pure logic, deterministic +- Use fake structs to isolate behavior +- No file I/O, network, or external dependencies +- Fast execution (<1ms per test) + +**Example:** + +```julia +Test.@testset "UNIT TESTS - Flow Types" begin + Test.@testset "Flow construction" begin + flow = Flows.Flow(fake_system, fake_integrator) + Test.@test flow isa Flows.Flow + Test.@test flow isa Flows.AbstractFlow + end +end +``` + +--- + +### 2. Integration Tests + +**Purpose**: Test interaction between multiple components through a complete workflow. + +**Characteristics:** + +- Exercise several real module boundaries together +- Use fakes only for leaf dependencies (not for the component chain being tested) +- Slower execution (acceptable up to 1s per test) + +**Example:** + +```julia +Test.@testset "INTEGRATION TESTS" begin + Test.@testset "flow building and system access" begin + sys = FakeSystem(2) # fake leaf dependency + integrator = FakeIntegrator() + + # build_flow exercises Flows + Systems + Integrators together + flow = Flows.build_flow(sys, integrator) + Test.@test flow isa Flows.AbstractFlow + Test.@test Flows.system(flow) isa Systems.AbstractSystem + Test.@test Flows.integrator(flow) isa Integrators.AbstractIntegrator + end +end +``` + +--- + +### 3. Contract Tests + +**Purpose**: Verify API contracts using fake implementations. + +**Characteristics:** + +- Define minimal fake types at top-level (never inside test functions) +- Implement only required contract methods +- Test routing, defaults, and error paths +- Verify Liskov Substitution Principle + +**Example:** + +```julia +# TOP-LEVEL: Fake type for contract testing +struct FakeSystem <: Systems.AbstractStateSystem{Common.Autonomous, Common.Fixed} + state_dim::Int +end + +# Implement contract +Systems.rhs(sys::FakeSystem) = (du, u, p, t) -> nothing +Systems.state_dimension(sys::FakeSystem) = sys.state_dim + +# Test contract +Test.@testset "Contract Implementation" begin + sys = FakeSystem(2) + Test.@test Systems.state_dimension(sys) == 2 +end +``` + +--- + +### 4. Error Tests + +**Purpose**: Verify that stubs and error paths throw the right exception with a useful message. + +Two sub-cases must be distinguished: + +#### 4a. Interface Stubs (`NotImplemented`) + +A fake that inherits from an abstract type but does **not** implement a required contract method will trigger the abstract type's stub, which throws `NotImplemented`. + +```julia +# TOP-LEVEL: fake that intentionally omits the required method +struct StubIntegrator <: Integrators.AbstractIntegrator end +# (no Integrators.integrate method defined for StubIntegrator) + +Test.@testset "Interface stubs" begin + Test.@testset "integrate throws NotImplemented" begin + stub = StubIntegrator() + Test.@test_throws Exceptions.NotImplemented Integrators.integrate(stub, sys, t0, x0, tf) + end +end +``` + +#### 4b. Extension Stubs (`ExtensionError`) + +Extension stubs throw `ExtensionError` when the required weak dependency is missing. **Always use a fake type** โ€” never a real type โ€” so that the test is independent of whether another test file happens to load the extension. + +```julia +# TOP-LEVEL: fake tag for which no extension registers an implementation +struct FakeExtTag <: Common.AbstractTag end + +Test.@testset "Extension stubs" begin + Test.@testset "unknown tag returns missing (fallback behavior)" begin + result = Integrators.__default_sciml_algorithm(FakeExtTag) + Test.@test result === missing + end +end +``` + +> **Why fake types?** If a real type is used and the extension is loaded (by any other test in the suite), the stub is replaced by the real implementation and the test silently passes or fails for the wrong reason. + +--- + +### Separation Pattern + +Use section comments to visually separate categories within a single testset: + +```julia +function test_flow_components() + Test.@testset "Flow Components" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + # Pure unit tests + end + + # ==================================================================== + # CONTRACT TESTS + # ==================================================================== + + Test.@testset "Contract Implementation" begin + # Contract tests with fakes + end + + # ==================================================================== + # INTEGRATION TESTS + # ==================================================================== + + Test.@testset "Integration" begin + # Multi-component tests + end + + # ==================================================================== + # ERROR TESTS + # ==================================================================== + + Test.@testset "Error Cases" begin + # Exception and edge-case tests + end + + end +end +``` + +## Critical Rules + +### Rule 1 โ€” Struct Definitions at Top-Level + +**NEVER define `struct`s inside test functions.** All helper types, mocks, and fakes must be defined at the **module top-level**. + +**โŒ Wrong:** + +```julia +function test_something() + Test.@testset "Test" begin + struct FakeType end # WRONG! Causes world-age issues + end +end +``` + +**โœ… Correct:** + +```julia +module TestSomething + +# TOP-LEVEL: Define all structs here +struct FakeType end + +function test_something() + Test.@testset "Test" begin + obj = FakeType() # Correct + end +end + +end # module +``` + +--- + +### Rule 2 โ€” Import and Qualification Rules + +**Use `import` instead of `using`** to avoid namespace pollution: + +```julia +import Test +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Data +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTSolvers: CTSolvers +``` + +**Always qualify method calls**, omitting the root module for submodules: + +**โœ… Correct:** + +```julia +Test.@test_throws Exceptions.IncorrectArgument invalid_call() +Test.@test Flows.system(flow) isa Systems.AbstractSystem +Test.@test Integrators.integrate(integrator, sys, t0, x0, tf) isa Integrators.AbstractIntegrationResult +``` + +**โŒ Wrong:** + +```julia +Test.@test_throws CTBase.Exceptions.IncorrectArgument invalid_call() # Too verbose +Test.@test CTFlows.Flows.system(flow) isa CTFlows.Systems.AbstractSystem # Too verbose +Test.@test true # Ambiguous +``` + +**Why:** Explicit qualification makes test intent clear while avoiding excessive verbosity. + +--- + +### Rule 3 โ€” Export Verification + +Add dedicated tests to verify exports and internal symbols: + +```julia +Test.@testset "Exports Verification" begin + Test.@testset "Submodule exports" begin + for sym in (:AbstractFlow, :Flow, :build_flow) + Test.@test isdefined(Flows, sym) + end + end + + Test.@testset "Package-level non-exports" begin + for sym in (:_internal_helper,) + Test.@test isdefined(Flows, sym) # exists in submodule + Test.@test !isdefined(CTFlows, sym) # not re-exported at package level + end + end +end +``` + +--- + +### Rule 4 โ€” Testing Internal Functions + +**Internal functions (prefixed with `_`) should be tested** when they contain significant logic. + +**Direct testing** โ€” preferred when logic is complex or has multiple branches: + +```julia +Test.@testset "Internal Function Tests" begin + result = Flows._validate_flow(flow, config) + Test.@test result isa Bool + Test.@test result == true +end +``` + +**Indirect testing** โ€” acceptable when logic is simple or already covered by integration tests: + +```julia +Test.@testset "build_flow - validation" begin + flow = Flows.build_flow(sys, integrator) + Test.@test Flows.system(flow) isa Systems.AbstractSystem +end +``` + +**When to test directly:** + +- Complex logic with multiple branches +- Error handling paths +- Edge cases hard to trigger via public API + +**When to test indirectly:** + +- Simple delegation or data transformation +- Logic already covered by integration tests +- Implementation details likely to change + +--- + +### Rule 5 โ€” Test Independence + +Each test must be independent and not rely on execution order. Create fresh instances inside each testset: + +**โœ… Correct:** + +```julia +Test.@testset "Test A" begin + flow = Flows.build_flow(FakeSystem(2), FakeIntegrator()) + # Test A logic +end + +Test.@testset "Test B" begin + flow = Flows.build_flow(FakeSystem(2), FakeIntegrator()) # Fresh instance + # Test B logic +end +``` + +**โŒ Wrong:** + +```julia +flow = Flows.build_flow(...) # Shared state + +Test.@testset "Test A" begin + # Modifies flow โ€” affects Test B! +end +``` + +--- + +### Rule 6 โ€” Stub Testing + +Two kinds of stubs exist in CTFlows; each requires a different approach. + +#### 6a. Interface Stubs (`NotImplemented`) + +An abstract type's default method throws `NotImplemented` when a concrete subtype has not provided an implementation. Test this by creating a **fake that omits the required method**. + +```julia +# TOP-LEVEL: fake that deliberately does NOT implement integrate +struct StubIntegrator <: Integrators.AbstractIntegrator end + +Test.@testset "Interface stubs" begin + Test.@testset "integrate stub throws NotImplemented" begin + stub = StubIntegrator() + Test.@test_throws Exceptions.NotImplemented Integrators.integrate(stub, sys, t0, x0, tf) + end +end +``` + +This test is safe regardless of which extensions are loaded. + +#### 6b. Extension Stubs (`ExtensionError` / fallback behavior) + +Extension code registers implementations for **known** types. When called with an **unknown** type, the stub (fallback) returns `missing` or throws `ExtensionError`. **Always use a fake type** that no extension knows about. + +**Why:** If a real type is used (e.g., `Integrators.SciML`) and the extension is loaded by another test file, the stub is replaced by the real implementation โ€” the test silently passes or fails for the wrong reason. + +```julia +# TOP-LEVEL: fake tag โ€” no extension registers an impl for this +struct FakeExtTag <: Common.AbstractTag end + +Test.@testset "Extension stubs" begin + Test.@testset "unknown tag โ†’ fallback returns missing" begin + result = Integrators.__default_sciml_algorithm(FakeExtTag) + Test.@test result === missing + end +end +``` + +**โŒ Wrong โ€” real type, load-order dependent:** + +```julia +# FAILS when CTFlowsSciML is loaded elsewhere in the suite +Test.@test_throws Exceptions.ExtensionError Integrators.SciML() +``` + +**Allowed with real types:** + +- Type hierarchy checks: `Test.@test Integrators.SciMLIntegrator <: Integrators.AbstractIntegrator` +- Pure metadata methods that don't depend on extension state (`id`, `description`) + +--- + +## Test Quality Standards + +### Assertion Quality + +**Use specific assertions:** + +**โœ… Good:** + +```julia +Test.@test result โ‰ˆ 1.23 atol=1e-10 +Test.@test obj isa Systems.AbstractSystem +Test.@test length(components) == 2 +Test.@test status == :first_order +``` + +**โŒ Poor:** + +```julia +Test.@test result > 0 # Too vague +Test.@test obj != nothing # Use Test.@test !isnothing(obj) +Test.@test true # Meaningless +``` + +### Test Naming + +Test names should describe **what** is being tested, not **how**: + +**โœ… Good:** + +```julia +Test.@testset "System construction" +Test.@testset "Contract Implementation - NotImplemented errors" +Test.@testset "Complete workflow - flow building" +``` + +**โŒ Poor:** + +```julia +Test.@testset "Test 1" +Test.@testset "Builder" +Test.@testset "Check stuff" +``` + +## Coverage Requirements + +### What to Test + +**Must test:** + +- โœ… Public API functions and types +- โœ… Contract implementations +- โœ… Error paths and exception handling +- โœ… Edge cases (empty inputs, boundary values, special cases) +- โœ… Type stability (for performance-critical code) +- โœ… Integration between components + +**Should test:** + +- โš ๏ธ Internal functions with complex logic +- โš ๏ธ Validation logic +- โš ๏ธ Conversion and transformation functions + +**Don't test:** + +- โŒ Trivial getters/setters without logic +- โŒ External library behavior +- โŒ Generated code (unless custom logic added) + +### Performance and Type Stability Tests + +For performance-critical code, add type stability and allocation tests. + +**See also:** `type-stability` rule for comprehensive standards. + +#### Type Stability Tests + +`@inferred` only works on **function calls**, not direct field access: + +```julia +Test.@testset "Type Stability" begin + sys = FakeSystem(2) + flow = build_test_flow(sys) # helper defined at module top-level + + Test.@test_nowarn Test.@inferred Flows.system(flow) + Test.@test_nowarn Test.@inferred Integrators.integrate(integrator, sys, t0, x0, tf) + + # โŒ WRONG: @inferred on field access + # Test.@inferred flow.system # ERROR! + + # โœ… CORRECT: wrap in a function call + Test.@test_nowarn Test.@inferred Flows.system(flow) +end +``` + +#### Allocation Tests + +```julia +Test.@testset "Allocations" begin + sys = FakeSystem(2) + allocs = Test.@allocated Systems.state_dimension(sys) + Test.@test allocs == 0 +end +``` + +## Anti-Patterns to Avoid + +### โŒ Don't: Test implementation details + +```julia +# BAD: Testing internal field names +Test.@test obj._internal_cache == something +``` + +### โŒ Don't: Use global mutable state + +```julia +# BAD: Global state between tests +const GLOBAL_COUNTER = Ref(0) + +Test.@testset "Test A" begin + GLOBAL_COUNTER[] += 1 # Affects other tests! +end +``` + +### โŒ Don't: Depend on test execution order + +```julia +# BAD: Test B depends on Test A running first +Test.@testset "Test A" begin + global shared_data = compute_something() +end + +Test.@testset "Test B" begin + Test.@test shared_data > 0 # Breaks if A doesn't run first! +end +``` + +## Quality Checklist + +Before finalizing tests, verify: + +- [ ] All structs defined at module top-level +- [ ] Unit and integration tests clearly separated +- [ ] Method calls are qualified (e.g., `Systems.function_name`) +- [ ] Test names describe what is being tested +- [ ] Each test is independent and deterministic +- [ ] Error cases are tested with `@test_throws` +- [ ] No file I/O or external dependencies in unit tests +- [ ] Fake types implement minimal contracts +- [ ] Tests document non-obvious logic +- [ ] No global mutable state +- [ ] Tests pass locally before committing + +## References + +- Test execution: `testing-execution` rule (`.windsurf/rules/testing-execution.md`) +- Test runner entry point: `test/runtests.jl` diff --git a/.windsurf/rules/testing-execution.md b/.windsurf/rules/testing-execution.md new file mode 100644 index 00000000..64f5bdc0 --- /dev/null +++ b/.windsurf/rules/testing-execution.md @@ -0,0 +1,104 @@ +--- +trigger: model_decision +--- + +# Julia Test Execution Guide + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿงช **Applying Testing Rule**: [specific testing principle being applied]" + +This ensures transparency about which testing standard is being used and why. + +--- + +This document defines how to run tests for the CTFlows.jl project. For test creation standards, see `testing.md`. + +## Running Tests + +For detailed instructions on how to run tests (specific tests, test groups, or all tests), please refer to the testing guide: + +**See**: `test/README.md` + +This file contains comprehensive information about: + +- Running all enabled tests +- Running specific test groups using glob patterns +- Running individual test files +- Running all tests including optional/long tests +- Generating coverage reports + +### Capturing Test Output (Agents) + +When running tests from the terminal (especially AI agents), **always pipe the output to a file via `tee`** instead of truncating with `tail -N`. Truncated output frequently hides the first failure or the compilation errors that trigger subsequent issues, forcing a second run. + +**โœ… Good โ€” capture full log, inspect tail afterwards:** + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems/test_abstract_system"])' \ + 2>&1 | tee /tmp/ctflows_test.log +# then, if needed, grep/tail the saved log without rerunning: +grep -E "Error|Fail|Test Summary" /tmp/ctflows_test.log +tail -200 /tmp/ctflows_test.log +``` + +**โŒ Avoid โ€” truncated stream lost if you need more context:** + +```bash +julia --project -e '...' 2>&1 | tail -120 # if the relevant error is above line -120, you must rerun +``` + +**Rules of thumb:** + +- Save to `/tmp/<pkg>_<scope>.log` (e.g., `/tmp/ctflows_test_systems.log`) so multiple concurrent sessions do not collide. +- Prefer `tee` on the full run; then use `grep`, `rg`, `tail`, or `less` on the saved file. +- Clean up `/tmp/*.log` files periodically; do not commit them. + +## Quick Test Commands + +### Convenience Alias: `jtest` + +If the `jtest` alias is defined in your shell (typically in `~/.zsh_aliases`), you can use it as a shortcut: + +```bash +# Alias definition (in ~/.zsh_aliases) +alias jtest="/Users/ocots/bin/julia_test.sh" +``` + +The `jtest` script wraps `Pkg.test()`: + +```bash +# Run all tests +jtest + +# Run specific test suite +jtest suite/systems/test_abstract_system +``` + +**Script location**: `~/bin/julia_test.sh` (user-local). If you have this alias, it provides a convenient way to run tests without typing the full Julia invocation each time. + +### Run all tests + +```bash +julia --project=@. -e 'using Pkg; Pkg.test()' +``` + +### Run specific test group + +```bash +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["ocp"])' +``` + +### Generate coverage report + +```bash +julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; coverage=true); include("test/coverage.jl")' +``` + +## References + +- Test README: `test/README.md` +- Test creation standards: `testing.md` +- Test workflows: `@/test-julia`, `@/test-julia-debug` +- Shared test problems: `test/problems/TestProblems.jl` +- Test runner: Uses `CTBase.TestRunner` extension diff --git a/.windsurf/rules/type-stability.md b/.windsurf/rules/type-stability.md new file mode 100644 index 00000000..54695506 --- /dev/null +++ b/.windsurf/rules/type-stability.md @@ -0,0 +1,291 @@ +--- +trigger: model_decision +--- + +# Julia Type Stability Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ”ง **Applying Type Stability Rule**: [specific type stability principle being applied]" + +This ensures transparency about which type stability standard is being used and why. + +--- + +This document defines type stability standards for the Control Toolbox project. Type stability is crucial for Julia performance and must be carefully considered in performance-critical code paths. + +## Core Principles + +1. **Type Inference**: The compiler must be able to determine return types from input types +2. **Performance**: Type-stable code is typically 10-100x faster than type-unstable code +3. **Testability**: Type stability must be verified with `@inferred` tests +4. **Clarity**: Type-stable code is often clearer and more maintainable + +## What is Type Stability? + +A function is **type-stable** if the type of its return value can be inferred from the types of its inputs at compile time. + +```julia +# โœ… Type-stable: return type is always Int +function get_dimension(ocp::OptimalControlProblem)::Int + return ocp.state_dimension +end + +# โŒ Type-unstable: return type depends on runtime value +function get_value(dict::Dict{Symbol, Any}, key::Symbol) + return dict[key] # Could be Int, Float64, String, anything! +end +``` + +## Testing Type Stability + +### Using `@inferred` + +```julia +@testset "Type Stability" begin + ocp = create_test_ocp() + + @test_nowarn @inferred get_dimension(ocp) + @test_nowarn @inferred state_dimension(ocp) + @test_nowarn @inferred process_constraint(ocp, :initial) +end +``` + +### Common Mistake: Testing Non-Functions + +```julia +# โŒ WRONG: @inferred on field access +@inferred ocp.state_dimension # ERROR: Not a function call! + +# โœ… CORRECT: Wrap in a function +function get_state_dim(ocp) + return ocp.state_dimension +end +@inferred get_state_dim(ocp) +``` + +### Using `@code_warntype` + +```julia +julia> @code_warntype get_value(dict, :key) +# Look for red "Any" or yellow warnings in the output +``` + +**What to look for:** +- Red `Any` or `Union{...}` in return type +- Yellow warnings about type instabilities +- Multiple possible return types + +## Type-Stable Structures + +### Use Parametric Types + +**โŒ Type-Unstable:** + +```julia +struct OptionDefinition + name::Symbol + type::Type + default::Any # Type-unstable! +end +``` + +**โœ… Type-Stable:** + +```julia +struct OptionDefinition{T} + name::Symbol + type::Type{T} + default::T # Type-stable! +end + +function get_default(opt::OptionDefinition{T}) where T + return opt.default # Return type: T +end +``` + +### Use NamedTuple Instead of Dict + +**โŒ Type-Unstable:** + +```julia +struct StrategyMetadata + specs::Dict{Symbol, OptionDefinition} # Values have unknown types +end +``` + +**โœ… Type-Stable:** + +```julia +struct StrategyMetadata{NT <: NamedTuple} + specs::NT # Type-stable with known keys +end +``` + +### Avoid Abstract Types in Structs + +**โŒ Type-Unstable:** + +```julia +struct Container + items::Vector{Number} # Abstract type! +end +``` + +**โœ… Type-Stable:** + +```julia +struct Container{T <: Number} + items::Vector{T} # Concrete type parameter +end +``` + +## Common Type Instabilities + +### 1. Untyped Containers + +```julia +# โŒ Type-unstable +results = [] # Vector{Any} + +# โœ… Type-stable +results = Int[] +``` + +### 2. Conditional Return Types + +```julia +# โŒ Type-unstable: Union{Int, Nothing} +function get_value(x::Int) + if x > 0 + return x + else + return nothing + end +end + +# โœ… Type-stable +function get_value(x::Int)::Int + return x > 0 ? x : 0 +end +``` + +### 3. Global Variables + +```julia +# โŒ Type-unstable +global_counter = 0 + +# โœ… Type-stable +const GLOBAL_COUNTER = Ref(0) +``` + +### 4. Type-Unstable Fields + +```julia +# โŒ Type-unstable +mutable struct Cache + data::Any +end + +# โœ… Type-stable +mutable struct Cache{T} + data::T +end +``` + +## Fixing Type Instabilities + +### Strategy 1: Add Type Annotations + +```julia +function process(x::Vector{Float64}) + result = Float64[] + # ... +end +``` + +### Strategy 2: Use Function Barriers + +```julia +# Type-unstable outer function +function outer(data::Dict{Symbol, Any}) + value = data[:key] # Type-unstable + return inner(value) # Function barrier isolates instability +end + +# Type-stable inner function +function inner(value::Int) + return value^2 +end +``` + +### Strategy 3: Parametric Types + +```julia +# Before +struct Container + data::Vector{Any} +end + +# After +struct Container{T} + data::Vector{T} +end +``` + +## When Type Stability Matters + +### Critical Paths (must be type-stable) + +- Inner loops (called millions of times) +- Hot paths in solvers +- Numerical computations +- Real-time systems + +### Less Critical Paths + +- One-time setup code +- User-facing API layers +- Error handling paths +- Debugging utilities + +## Performance Testing + +```julia +@testset "Allocations" begin + ocp = create_test_ocp() + + allocs = @allocated state_dimension(ocp) + @test allocs == 0 + + allocs = @allocated build_model(ocp) + @test allocs < 1000 # bytes +end +``` + +## Quality Checklist + +Before finalizing code, verify: + +- [ ] Critical functions tested with `@inferred` +- [ ] No `Any` types in hot paths +- [ ] Parametric types used where appropriate +- [ ] `@code_warntype` shows no red flags +- [ ] Allocation tests pass for critical operations +- [ ] Benchmarks meet performance targets + +## Key Takeaways + +1. Type stability is crucial for Julia performance +2. Test with `@inferred` for all critical functions +3. Use parametric types and NamedTuple for type-stable structures +4. Avoid `Any` and abstract types in hot paths +5. Use `@code_warntype` to debug instabilities +6. Test allocations for performance-critical code + +## Related Skills + +- `performance` rule โ€” profiling, benchmarking, allocation reduction +- `testing-creation` rule โ€” type stability test patterns +- `architecture` rule โ€” parametric type design diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..2920a3a6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,66 @@ +# CTFlows.jl โ€” Agent Navigation Guide + +Quick-reference for Cascade: project architecture, available skills, and active rules. + +--- + +## Project Overview + +**CTFlows.jl** is a Julia package in the [control-toolbox](https://github.com/control-toolbox) ecosystem. +It provides the **flow layer** for optimal control problems: assembling systems, integrating ODEs, and building solutions. + +**Stack position:** CTBase โ†’ CTModels โ†’ **CTFlows** โ†’ CTSolvers โ†’ OptimalControl + +--- + +## Source Architecture + +Submodule layout (all public symbols accessed via qualified paths โ€” no top-level exports): + +``` +src/ +โ”œโ”€โ”€ CTFlows.jl # Top-level manifest โ€” exports nothing +โ”œโ”€โ”€ Common/ # Shared types and tags (AbstractTag, AbstractTrait, โ€ฆ) +โ”œโ”€โ”€ Data/ # Vector fields and Hamiltonian data structures +โ”œโ”€โ”€ Flows/ # AbstractFlow, Flow, MultiPhaseFlow, building logic +โ””โ”€โ”€ Integrators/ # AbstractODEIntegrator, building logic + +ext/ +โ”œโ”€โ”€ CTFlowsSciML/ # SciML ODE integration extension +โ”œโ”€โ”€ CTFlowsForwardDiff.jl +โ”œโ”€โ”€ CTFlowsOrdinaryDiffEqTsit5.jl +โ”œโ”€โ”€ CTFlowsPlots.jl +โ””โ”€โ”€ CTFlowsStaticArrays.jl + +test/suite/ # Tests organised by functionality (not by src layout) +docs/ # Documenter.jl site (auto-generated API via CTBase) +``` + +--- + +## Windsurf Rules (always active) + +| Rule | Trigger | Purpose | +|---|---|---| +| `architecture.md` | โ€” | Introducing new types, restructuring modules, reviewing SOLID/patterns | +| `docstrings.md` | โ€” | Writing or reviewing Julia docstrings | +| `documentation.md` | `glob: docs/**/*` | Documenter.jl layout, `make.jl` template, `api_reference.jl`, `InterLinks` setup | +| `exceptions.md` | โ€” | Adding error paths, contract stubs, argument validation | +| `modules.md` | `glob: src/**/*.jl, ext/**/*.jl` | Submodule conventions: qualified imports, manifest pattern, export policy, DAG ordering | +| `performance.md` | โ€” | Hot paths, inner loops, profiling, benchmarking | +| `plan.md` | โ€” | Writing an implementation plan before coding | +| `testing-creation.md` | โ€” | Writing or reviewing test files under `test/suite/` | +| `testing-execution.md` | `model_decision` | How to run tests (commands, `tee` capture, `jtest` alias) | +| `type-stability.md` | โ€” | New structs, parametric types, `@inferred` test design | + +Rules live in `.windsurf/rules/`. + +--- + +## Key Conventions + +- **No top-level exports** โ€” use `CTFlows.Submodule.symbol` everywhere. +- **Qualified imports** โ€” `using PackageName: PackageName`, never bare `using`. +- **Fake types at module top-level** โ€” never inside test functions. +- **Plans before code** โ€” write a full plan (see `plan.md` rule) before touching files. +- **Docstrings last** โ€” written only after all implementation steps are stable. diff --git a/Project.toml b/Project.toml index f7cad73c..720e0403 100644 --- a/Project.toml +++ b/Project.toml @@ -1,51 +1,59 @@ name = "CTFlows" uuid = "1c39547c-7794-42f7-af83-d98194f657c2" +version = "0.9.0-beta" authors = ["Olivier Cots <olivier.cots@toulouse-inp.fr>"] -version = "0.8.25" [deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" -MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" [weakdeps] -OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" - -[extensions] -CTFlowsODE = "OrdinaryDiffEq" - -[extras] -Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -OrdinaryDiffEqRosenbrock = "43230ef6-c299-4910-a778-202eb28ce4ce" -OrdinaryDiffEqSDIRK = "2d112036-d095-4a1e-ab9a-08536f3ecdbf" -OrdinaryDiffEqBDF = "6ad6398a-0878-4a85-9266-38940aa047c8" -OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -[targets] -test = ["Aqua", "CTParser", "OrdinaryDiffEq", "Plots", "Test", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqSDIRK", "OrdinaryDiffEqBDF", "OrdinaryDiffEqDefault"] +[extensions] +CTFlowsDifferentiationInterface = ["DifferentiationInterface"] +CTFlowsForwardDiff = ["ForwardDiff"] +CTFlowsOrdinaryDiffEqTsit5 = ["OrdinaryDiffEqTsit5"] +CTFlowsPlots = ["Plots"] +CTFlowsSciML = ["DiffEqBase", "SciMLBase"] +CTFlowsStaticArrays = ["StaticArrays"] [compat] +ADTypes = "1" Aqua = "0.8" CTBase = "0.18" CTModels = "0.10" -CTParser = "0.8" +CTSolvers = "0.4" +CommonSolve = "0.2" +DiffEqBase = "7" +DifferentiationInterface = "0.7" DocStringExtensions = "0.9" -ForwardDiff = "0.10, 1.0" -LinearAlgebra = "1" -MLStyle = "0.4" -MacroTools = "0.5" -OrdinaryDiffEq = "6" +ForwardDiff = "0.10, 1" +OrdinaryDiffEqTsit5 = "2" Plots = "1" +RecipesBase = "1" +Reexport = "1" +SciMLBase = "3" +StaticArrays = "1" Test = "1" julia = "1.10" -OrdinaryDiffEqRosenbrock = "=1.27" -OrdinaryDiffEqSDIRK = "=1.13" -OrdinaryDiffEqBDF = "=1.23" -OrdinaryDiffEqDefault = "=1.13" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "DiffEqBase", "DifferentiationInterface", "ForwardDiff", "OrdinaryDiffEqTsit5", "Plots", "SciMLBase", "StaticArrays", "Test"] diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..ae08f1be --- /dev/null +++ b/_typos.toml @@ -0,0 +1,14 @@ +[default] +locale = "en" +extend-ignore-re = [ + "OT", +] + +[files] +extend-exclude = [ + "*.json", + "*.toml", + "*.svg", + "save/**", + "reports/**", +] \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 308212e8..bed431b5 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,12 +1,26 @@ [deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" -OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" [compat] +CTBase = "0.18" CTModels = "0.10" +CTSolvers = "0.4" +DiffEqBase = "7" Documenter = "1" -DocumenterMermaid = "0.2" -OrdinaryDiffEq = "6" +ForwardDiff = "0.10, 1" +MarkdownAST = "0.1" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +SciMLBase = "3" julia = "1.10" diff --git a/docs/api_reference.jl b/docs/api_reference.jl new file mode 100644 index 00000000..cc130361 --- /dev/null +++ b/docs/api_reference.jl @@ -0,0 +1,219 @@ +# ============================================================================== +# CTFlows API Reference Manager +# +# Generates API reference pages via CTBase.automatic_reference_documentation, +# one section per CTFlows submodule. Generated .md files are cleaned up after +# the build. +# ============================================================================== + +""" + generate_api_reference(src_dir::String, ext_dir::String) + +Generate the API reference documentation for CTFlows. +Returns the list of pages. +""" +function generate_api_reference(src_dir::String, ext_dir::String) + # Helper to build absolute paths + src(files...) = [abspath(joinpath(src_dir, f)) for f in files] + ext(files...) = [abspath(joinpath(ext_dir, f)) for f in files] + + EXCLUDE_SYMBOLS = Symbol[:include, :eval] + + pages = [ + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Common + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.Common => src( + joinpath("Common", "Common.jl"), + joinpath("Common", "abstract_tag.jl"), + joinpath("Common", "configs.jl"), + joinpath("Common", "default.jl"), + joinpath("Common", "internal_norm.jl"), + joinpath("Common", "ode_parameters.jl"), + joinpath("Common", "traits.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="Common", + title_in_menu="Common", + filename="api_common", + ), + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Data + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.Data => src( + joinpath("Data", "Data.jl"), + joinpath("Data", "vector_field.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="Data", + title_in_menu="Data", + filename="api_data", + ), + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Systems + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.Systems => src( + joinpath("Systems", "Systems.jl"), + joinpath("Systems", "abstract_system.jl"), + joinpath("Systems", "building.jl"), + joinpath("Systems", "vector_field_system.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="Systems", + title_in_menu="Systems", + filename="api_systems", + ), + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Integrators + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.Integrators => src( + joinpath("Integrators", "Integrators.jl"), + joinpath("Integrators", "abstract_integrator.jl"), + joinpath("Integrators", "building.jl"), + joinpath("Integrators", "integration_result.jl"), + joinpath("Integrators", "sciml.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="Integrators", + title_in_menu="Integrators", + filename="api_integrators", + ), + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Flows + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.Flows => src( + joinpath("Flows", "Flows.jl"), + joinpath("Flows", "abstract_flow.jl"), + joinpath("Flows", "building.jl"), + joinpath("Flows", "calling.jl"), + joinpath("Flows", "flow.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="Flows", + title_in_menu="Flows", + filename="api_flows", + ), + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Solutions + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.Solutions => src( + joinpath("Solutions", "Solutions.jl"), + joinpath("Solutions", "building.jl"), + joinpath("Solutions", "vector_field_solution.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="Solutions", + title_in_menu="Solutions", + filename="api_solutions", + ), + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # MultiPhase + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[ + CTFlows.MultiPhase => src( + joinpath("MultiPhase", "MultiPhase.jl"), + joinpath("MultiPhase", "calling.jl"), + joinpath("MultiPhase", "concatenation.jl"), + joinpath("MultiPhase", "multiphase_flow.jl"), + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="MultiPhase", + title_in_menu="MultiPhase", + filename="api_multiphase", + ), + ] + + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Extension: ForwardDiff + # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + CTFlowsForwardDiff = Base.get_extension(CTFlows, :CTFlowsForwardDiff) + if !isnothing(CTFlowsForwardDiff) + push!( + pages, + CTBase.automatic_reference_documentation(; + subdirectory="api", + primary_modules=[CTFlowsForwardDiff => ext("CTFlowsForwardDiff.jl")], + external_modules_to_document=[CTFlows], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=true, + title="ForwardDiff Extension", + title_in_menu="ForwardDiff", + filename="ext_forwarddiff", + ), + ) + end + + return pages +end + +""" + with_api_reference(f::Function, src_dir::String, ext_dir::String) + +Generate the API reference, execute `f(pages)`, then clean up generated `.md` files. +""" +function with_api_reference(f::Function, src_dir::String, ext_dir::String) + pages = generate_api_reference(src_dir, ext_dir) + try + f(pages) + finally + docs_src = abspath(joinpath(@__DIR__, "src")) + _cleanup_pages(docs_src, pages) + end +end + +function _cleanup_pages(docs_src::String, pages) + for p in pages + content = last(p) + if content isa AbstractString + fname = endswith(content, ".md") ? content : content * ".md" + full_path = joinpath(docs_src, fname) + if isfile(full_path) + rm(full_path) + end + elseif content isa Vector + _cleanup_pages(docs_src, content) + end + end +end diff --git a/docs/make.jl b/docs/make.jl index f3d1af42..4a903649 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,47 +1,94 @@ -using Documenter -using DocumenterMermaid +# to run the documentation generation: +# julia --project=. docs/make.jl +pushfirst!(LOAD_PATH, joinpath(@__DIR__)) +pushfirst!(LOAD_PATH, joinpath(@__DIR__, "..")) + +# control-toolbox packages using CTFlows +using CTBase using CTModels -using OrdinaryDiffEq - -# to add docstrings from external packages -const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) -Modules = [CTFlowsODE] -for Module in Modules - isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && - DocMeta.setdocmeta!(Module, :DocTestSetup, :(using $Module); recursive=true) -end -repo_url = "github.com/control-toolbox/CTFlows.jl" +# documentation +using DocumenterInterLinks +using Documenter +using Markdown +using MarkdownAST: MarkdownAST -API_PAGES = [ - "concatenation.md", - "ctflowsode.md", - "default.md", - "differential_geometry.md", - "ext_default.md", - "ext_types.md", - "ext_utils.md", - "function.md", - "hamiltonian.md", - "optimal_control_problem_utils.md", - "optimal_control_problem.md", - "types.md", - "utils.md", - "vector_field.md", -] - -makedocs(; - sitename="CTFlows.jl", - format=Documenter.HTML(; - repolink="https://" * repo_url, - prettyurls=false, - assets=[ - asset("https://control-toolbox.org/assets/css/documentation.css"), - asset("https://control-toolbox.org/assets/js/documentation.js"), - ], +# trigger extensions +using ForwardDiff +using OrdinaryDiffEqTsit5 +using Plots +using SciMLBase, DiffEqBase + +# +links = InterLinks( + "CTBase" => ( + "https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml"), + ), + "CTModels" => ( + "https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml"), ), - pages=["Introduction" => "index.md", "API" => API_PAGES], ) +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +# Configuration +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +# if draft is true, then the julia code from .md is not executed +# to disable the draft mode in a specific markdown file, use the following: +#= +```@meta +Draft = false +``` +=# +draft = false # Draft mode: if true, @example blocks in markdown are not executed + +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +# Load extensions +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) + +if !isnothing(DocumenterReference) + DocumenterReference.reset_config!() +end + +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +# Paths +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +repo_url = "github.com/control-toolbox/CTFlows.jl" +src_dir = abspath(joinpath(@__DIR__, "..", "src")) +ext_dir = abspath(joinpath(@__DIR__, "..", "ext")) + +# Include the API reference manager +include("api_reference.jl") + +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +# Build documentation +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +with_api_reference(src_dir, ext_dir) do api_pages + makedocs(; + draft=draft, + remotes=nothing, # Disable remote links. Needed for DocumenterReference + warnonly=true, + sitename="CTFlows.jl", + format=Documenter.HTML(; + repolink="https://" * repo_url, + prettyurls=false, + assets=[ + asset("https://control-toolbox.org/assets/css/documentation.css"), + asset("https://control-toolbox.org/assets/js/documentation.js"), + ], + ), + pages=["Introduction" => "index.md", "API Reference" => api_pages], + plugins=[links], + ) +end + +# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + deploydocs(; repo=repo_url * ".git", devbranch="main") diff --git a/docs/src/index.md b/docs/src/index.md index 1948e8aa..bca5b75f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,12 +1,113 @@ # CTFlows.jl +```@meta +CurrentModule = CTFlows +``` + The `CTFlows.jl` package is part of the [control-toolbox ecosystem](https://github.com/control-toolbox). +It provides the **flow integration layer** for systems and optimal control problems: + +- **abstract types** describing systems, flows, and the strategy families that build and integrate them; +- **pipeline functions** (`build_system`, `build_flow`, `integrate`, `build_solution`, `solve`) operating uniformly on the abstractions; +- a concrete [`Flow`](@ref) wrapper combining an [`AbstractSystem`](@ref) with an [`AbstractIntegrator`](@ref). + +!!! info "CTFlows in the ecosystem" + + **CTFlows** focuses on **integrating** systems (vector fields, OCP-derived Hamiltonian flows, โ€ฆ) + via pluggable strategies (modelers, integrators, AD backends). + For **modelling** optimal control problems, see [CTModels.jl](https://github.com/control-toolbox/CTModels.jl); + for **solving NLPs**, see [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl); + the umbrella package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl). + +!!! warning "Qualified API access" + + CTFlows exports nothing at the package level. All public symbols live in submodules and must + be accessed via qualified paths. + + ```julia + using CTFlows + sys = CTFlows.Systems.AbstractSystem # abstract type + flow = CTFlows.Flows.Flow(system, integ) # concrete flow + sol = CTFlows.Flows.call(flow, config) # integrate + ``` + + Or bring a single submodule into scope with `using CTFlows.Submodule`: + + ```julia + using CTFlows.Flows + sol = call(flow, config) + ``` + +## Architecture overview + +CTFlows organises its code into specialised submodules: + +| Layer | Submodule | Purpose | +|---|---|---| +| **Utilities** | [`Common`](@ref CTFlows.Common) | shared types, traits, and configuration | +| **Data** | [`Data`](@ref CTFlows.Data) | vector field data structures with traits | +| **Objects** | [`Systems`](@ref CTFlows.Systems), [`Flows`](@ref CTFlows.Flows) | what is acted upon (a fully-assembled system, or a callable flow) | +| **Strategy families** | [`Integrators`](@ref CTFlows.Integrators) | `<: CTSolvers.Strategies.AbstractStrategy` | +| **Solutions** | [`Solutions`](@ref CTFlows.Solutions) | solution types and solution building | + +## Contracts at a glance + +### `AbstractSystem` + +A fully-assembled object that can be integrated. Required methods: + +- `rhs!(system) โ†’ (du, u, p, t) -> nothing` โ€” returns the ODE right-hand side function -The root package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl) which aims to provide tools to model and solve optimal control problems with ordinary differential equations by direct and indirect methods, both on CPU and GPU. +Traits (automatically supported): -**API Documentation** +- `time_dependence(system)` โ€” returns `Autonomous` or `NonAutonomous` +- `variable_dependence(system)` โ€” returns `Fixed` or `NonFixed` -```@contents -Pages = Main.API_PAGES -Depth = 1 +### `AbstractFlow` + +A callable combining a system and an integrator. Required methods: + +- `system(flow)` โ€” returns the associated `AbstractSystem` +- `integrator(flow)` โ€” returns the associated `AbstractIntegrator` + +Traits (delegated to system): + +- `time_dependence(flow)`, `variable_dependence(flow)` โ€” forwarded to the system + +### Strategy families + +All inherit from `CTSolvers.Strategies.AbstractStrategy` and gain its full contract +(`id`, `metadata`, `options`, `Base.show`, `describe`, โ€ฆ). + +- [`AbstractIntegrator`](@ref CTFlows.Integrators.AbstractIntegrator): + callable `(integrator)(system, config; variable=nothing) โ†’ ode_problem` + +## API at a glance + +```julia +# Build a flow from vector field data +using CTFlows.Data, CTFlows.Flows, CTFlows.Common + +vf = Data.VectorField((t, x, v) -> x, Autonomous(), Fixed()) +flow = Flows.Flow(vf; reltol=1e-8) + +# Integrate using a configuration +config = Common.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) +sol = Flows.call(flow, config) + +# Or point-to-point integration +config = Common.StatePointConfig((0.0, 1.0), [1.0, 0.0]) +final_state = Flows.call(flow, config) ``` + +## Status + +The current implementation provides: + +- Abstract types with traits (`AbstractSystem`, `AbstractFlow`, `AbstractIntegrator`) +- Concrete implementations (`VectorField`, `VectorFieldSystem`, `Flow`, `VectorFieldSolution`) +- Integration pipeline via `Flows.call` with configuration objects +- SciML integrator strategy + +Future phases will add additional integrator strategies and solution types +(see the [roadmap](https://github.com/control-toolbox/CTFlows.jl/blob/main/reports/roadmap.md)). diff --git a/ext/CTFlowsDifferentiationInterface.jl b/ext/CTFlowsDifferentiationInterface.jl new file mode 100644 index 00000000..2c8236a9 --- /dev/null +++ b/ext/CTFlowsDifferentiationInterface.jl @@ -0,0 +1,333 @@ +""" + CTFlowsDifferentiationInterface + +Package extension providing DifferentiationInterface.jl backend implementations +for automatic differentiation in CTFlows Hamiltonian systems. + +Activated automatically when `DifferentiationInterface` is loaded together with `CTFlows`. + +This extension provides: +- `DifferentiationInterfaceCache` โ€” prepared gradient plans for efficient repeated computation +- `Differentiation.prepare_cache` โ€” cache preparation for `DifferentiationInterface` backend +- `Differentiation.hamiltonian_gradient` โ€” gradient computation with/without cache +- `Differentiation.variable_gradient` โ€” variable gradient with/without cache +""" +module CTFlowsDifferentiationInterface + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +using CTFlows: CTFlows +using CTFlows.Common: Common +using CTFlows.Data: Data +using CTFlows.Differentiation: Differentiation +using DifferentiationInterface: DifferentiationInterface as DI +import CTSolvers + +# ============================================================================== +# DifferentiationInterfaceCache โ€” Prepared gradient plans +# ============================================================================== + +""" +$(TYPEDEF) + +Cache for DifferentiationInterface.jl backend, storing prepared gradient plans +for efficient repeated computation of Hamiltonian gradients. + +# Fields +- `prep_x::PX`: Prepared plan for โˆ‚H/โˆ‚x (or `Nothing` if not prepared) +- `prep_p::PP`: Prepared plan for โˆ‚H/โˆ‚p (or `Nothing` if not prepared) +- `prep_v::PV`: Prepared plan for โˆ‚H/โˆ‚v (`Nothing` for Fixed problems) + +# See also +- [`CTFlows.Differentiation.prepare_cache`](@ref) +- [`CTFlows.Differentiation.hamiltonian_gradient`](@ref) +""" +mutable struct DifferentiationInterfaceCache{HX, HP, HV} <: Common.AbstractCache + p_x # DI prepared plan for โˆ‚H/โˆ‚x (or Nothing) + p_p # DI prepared plan for โˆ‚H/โˆ‚p (or Nothing) + p_v # DI prepared plan for โˆ‚H/โˆ‚v (Nothing if Fixed) + h_x::HX # Hamiltonian function for โˆ‚H/โˆ‚x + h_p::HP # Hamiltonian function for โˆ‚H/โˆ‚p + h_v::HV # Hamiltonian function for โˆ‚H/โˆ‚v +end + +# ============================================================================== +# Differentiation.prepare_cache โ€” cache preparation for DifferentiationInterface +# ============================================================================== +function preparator(::Type{<:Number}) + return DI.prepare_derivative +end + +function preparator(::Type{<:AbstractArray}) + return DI.prepare_gradient +end + + +""" +$(TYPEDSIGNATURES) + +Prepare a gradient cache for the `DifferentiationInterface` backend. + +# Arguments +- `backend::Differentiation.DifferentiationInterface`: The AD backend. +- `h::Data.AbstractHamiltonian`: The Hamiltonian function. +- `typical_x`: Typical initial state value (for type inference). +- `typical_p`: Typical initial costate value (for type inference). +- `typical_v`: Typical variable value (for type inference; `nothing` for Fixed problems). + +# Returns +- `DifferentiationInterfaceCache` if `prepare_cache=true` option is set. +- `nothing` if `prepare_cache=false` option is set (gradient methods fall back to plain `DI.gradient`). + +# Behavior +When the `prepare_cache` option is `true` (default), this function: +1. Extracts the DI backend and `do_prepare` flag from the strategy options. +2. Calls `DI.prepare_gradient` for each variable (โˆ‚H/โˆ‚x, โˆ‚H/โˆ‚p, โˆ‚H/โˆ‚v). +3. Returns a `DifferentiationInterfaceCache` storing the prepared plans. + +When `prepare_cache` is `false`, returns `nothing` and gradient methods compute +gradients on-the-fly using plain `DI.gradient`. + +# See also +- [`CTFlowsDifferentiationInterface.DifferentiationInterfaceCache`](@ref) +- [`CTFlows.Differentiation.hamiltonian_gradient`](@ref) +""" +function Differentiation.prepare_cache( + backend::Differentiation.DifferentiationInterface, + h::Data.AbstractHamiltonian, + typical_t, typical_x, typical_p, typical_v +) + opts = CTSolvers.Strategies.options(backend) + di_backend = opts[:ad_backend] + do_prepare = opts[:prepare_cache] + + if do_prepare + h_x(x, t, p, v) = h(t, x, p, v) + h_p(p, t, x, v) = h(t, x, p, v) + h_v(v, t, x, p) = h(t, x, p, v) + p_x = preparator(typeof(typical_x))(h_x, di_backend, typical_x, DI.Constant(typical_t), DI.Constant(typical_p), DI.Constant(typical_v)) + p_p = preparator(typeof(typical_p))(h_p, di_backend, typical_p, DI.Constant(typical_t), DI.Constant(typical_x), DI.Constant(typical_v)) + p_v = if typical_v === nothing + nothing + else + preparator(typeof(typical_v))(h_v, di_backend, typical_v, DI.Constant(typical_t), DI.Constant(typical_x), DI.Constant(typical_p)) + end + return DifferentiationInterfaceCache(p_x, p_p, p_v, h_x, h_p, h_v) + else + return nothing # caller gets Nothing; gradient methods fall back to plain DI.gradient + end +end + +function update!(cache::DifferentiationInterfaceCache, backend::Differentiation.DifferentiationInterface, t, x, p, v) + opts = CTSolvers.Strategies.options(backend) + di_backend = opts[:ad_backend] + do_prepare = opts[:prepare_cache] + if !do_prepare + return nothing + end + p_x = preparator(typeof(x))(cache.h_x, di_backend, x, DI.Constant(t), DI.Constant(p), DI.Constant(v)) + p_p = preparator(typeof(p))(cache.h_p, di_backend, p, DI.Constant(t), DI.Constant(x), DI.Constant(v)) + p_v = if v === nothing + nothing + else + preparator(typeof(v))(cache.h_v, di_backend, v, DI.Constant(t), DI.Constant(x), DI.Constant(p)) + end + cache.p_x = p_x + cache.p_p = p_p + cache.p_v = p_v + return nothing +end + +# ============================================================================== +# Differentiation.hamiltonian_gradient โ€” with/without cache +# ============================================================================== +function derivator(::Type{<:Number}) + return DI.derivative +end + +function derivator(::Type{<:AbstractArray}) + return DI.gradient +end + + +""" +$(TYPEDSIGNATURES) + +Compute Hamiltonian gradients (โˆ‚H/โˆ‚x, โˆ‚H/โˆ‚p) using plain `DI.gradient` (no cache). + +This overload is used when `cache::Nothing`, i.e., when the `prepare_cache` option +is `false` or when the system does not require cache preparation. + +# Arguments +- `backend::Differentiation.DifferentiationInterface`: The AD backend. +- `h`: The Hamiltonian function. +- `t`: Time. +- `x`: State. +- `p`: Costate. +- `v`: Variable (or `nothing` for Fixed problems). +- `::Nothing`: No prepared plan available. + +# Returns +- Tuple `(grad_x, grad_p)` where: + - `grad_x` = โˆ‚H/โˆ‚x + - `grad_p` = โˆ‚H/โˆ‚p + +# See also +- [`CTFlowsDifferentiationInterface.DifferentiationInterfaceCache`](@ref) +- [`CTFlows.Differentiation.prepare_cache`](@ref) +""" +function Differentiation.hamiltonian_gradient( + backend::Differentiation.DifferentiationInterface, h, t, x, p, v, + ::Nothing +) + di_backend = CTSolvers.Strategies.options(backend)[:ad_backend] + h_x(x, t, p, v) = h(t, x, p, v) + h_p(p, t, x, v) = h(t, x, p, v) + # Use derivative for scalars, gradient for arrays + grad_x = derivator(typeof(x))(h_x, di_backend, x, DI.Constant(t), DI.Constant(p), DI.Constant(v)) + grad_p = derivator(typeof(p))(h_p, di_backend, p, DI.Constant(t), DI.Constant(x), DI.Constant(v)) + return (grad_x, grad_p) +end + +""" +$(TYPEDSIGNATURES) + +Compute Hamiltonian gradients (โˆ‚H/โˆ‚x, โˆ‚H/โˆ‚p) using prepared gradient plans. + +This overload is used when `cache::DifferentiationInterfaceCache`, i.e., when +the `prepare_cache` option is `true` and the cache was prepared at flow call time. + +# Arguments +- `backend::Differentiation.DifferentiationInterface`: The AD backend. +- `h`: The Hamiltonian function. +- `t`: Time. +- `x`: State. +- `p`: Costate. +- `v`: Variable (or `nothing` for Fixed problems). +- `cache::DifferentiationInterfaceCache`: Prepared gradient plans from `prepare_cache`. + +# Returns +- Tuple `(grad_x, grad_p)` where: + - `grad_x` = โˆ‚H/โˆ‚x + - `grad_p` = โˆ‚H/โˆ‚p + +# Performance +Uses the prepared plans stored in `cache.prep_x` and `cache.prep_p` for efficient +repeated gradient computation during ODE integration. + +# See also +- [`CTFlowsDifferentiationInterface.DifferentiationInterfaceCache`](@ref) +- [`CTFlows.Differentiation.prepare_cache`](@ref) +""" +function Differentiation.hamiltonian_gradient( + backend::Differentiation.DifferentiationInterface, h, t, x, p, v, + cache::DifferentiationInterfaceCache +) + di_backend = CTSolvers.Strategies.options(backend)[:ad_backend] + try + # Use derivative for scalars, gradient for arrays + grad_x = derivator(typeof(x))(cache.h_x, cache.p_x, di_backend, x, DI.Constant(t), DI.Constant(p), DI.Constant(v)) + grad_p = derivator(typeof(p))(cache.h_p, cache.p_p, di_backend, p, DI.Constant(t), DI.Constant(x), DI.Constant(v)) + return (grad_x, grad_p) + catch e + if e isa DI.PreparationMismatchError + update!(cache, backend, t, x, p, v) # recompute cache + grad_x = derivator(typeof(x))(cache.h_x, cache.p_x, di_backend, x, DI.Constant(t), DI.Constant(p), DI.Constant(v)) + grad_p = derivator(typeof(p))(cache.h_p, cache.p_p, di_backend, p, DI.Constant(t), DI.Constant(x), DI.Constant(v)) + return (grad_x, grad_p) + else + rethrow(e) + end + end +end + +# ============================================================================== +# Differentiation.variable_gradient โ€” with/without cache +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Compute variable gradient (โˆ‚H/โˆ‚v) using plain `DI.gradient` (no cache). + +This overload is used when `cache::Nothing`, i.e., when the `prepare_cache` option +is `false` or when the system does not require cache preparation. + +# Arguments +- `backend::Differentiation.DifferentiationInterface`: The AD backend. +- `h`: The Hamiltonian function. +- `t`: Time. +- `x`: State. +- `p`: Costate. +- `v`: Variable (or `nothing` for Fixed problems). +- `cache::Nothing`: No prepared plan available. + +# Returns +- `nothing` for Fixed problems (`v === nothing`). +- `grad_v` = โˆ‚H/โˆ‚v for NonFixed problems. + +# See also +- [`CTFlowsDifferentiationInterface.DifferentiationInterfaceCache`](@ref) +- [`CTFlows.Differentiation.prepare_cache`](@ref) +""" +function Differentiation.variable_gradient( + backend::Differentiation.DifferentiationInterface, h, t, x, p, v, + ::Nothing +) + # For Fixed problems (v === nothing), return nothing without calling DI.gradient + di_backend = CTSolvers.Strategies.options(backend)[:ad_backend] + h_v(v, t, x, p) = h(t, x, p, v) + # Use derivative for scalars, gradient for arrays + grad_v = derivator(typeof(v))(h_v, di_backend, v, DI.Constant(t), DI.Constant(x), DI.Constant(p)) + return grad_v +end + +""" +$(TYPEDSIGNATURES) + +Compute variable gradient (โˆ‚H/โˆ‚v) using prepared gradient plans. + +This overload is used when `cache::DifferentiationInterfaceCache`, i.e., when +the `prepare_cache` option is `true` and the cache was prepared at flow call time. + +# Arguments +- `backend::Differentiation.DifferentiationInterface`: The AD backend. +- `h`: The Hamiltonian function. +- `t`: Time. +- `x`: State. +- `p`: Costate. +- `v`: Variable (or `nothing` for Fixed problems). +- `cache::DifferentiationInterfaceCache`: Prepared gradient plans from `prepare_cache`. + +# Returns +- `nothing` for Fixed problems (`v === nothing`). +- `grad_v` = โˆ‚H/โˆ‚v for NonFixed problems. + +# Performance +Uses the prepared plan stored in `cache.prep_v` for efficient repeated gradient +computation during ODE integration. + +# See also +- [`CTFlowsDifferentiationInterface.DifferentiationInterfaceCache`](@ref) +- [`CTFlows.Differentiation.prepare_cache`](@ref) +""" +function Differentiation.variable_gradient( + backend::Differentiation.DifferentiationInterface, h, t, x, p, v, + cache::DifferentiationInterfaceCache +) + di_backend = CTSolvers.Strategies.options(backend)[:ad_backend] + try + # Use derivative for scalars, gradient for arrays + grad_v = derivator(typeof(v))(cache.h_v, cache.p_v, di_backend, v, DI.Constant(t), DI.Constant(x), DI.Constant(p)) + return grad_v + catch e + if e isa DI.PreparationMismatchError + update!(cache, backend, t, x, p, v) # recompute cache + grad_v = derivator(typeof(v))(cache.h_v, cache.p_v, di_backend, v, DI.Constant(t), DI.Constant(x), DI.Constant(p)) + return grad_v + else + rethrow(e) + end + end +end + +end # module diff --git a/ext/CTFlowsForwardDiff.jl b/ext/CTFlowsForwardDiff.jl new file mode 100644 index 00000000..fe9c7a61 --- /dev/null +++ b/ext/CTFlowsForwardDiff.jl @@ -0,0 +1,80 @@ +""" + CTFlowsForwardDiff + +Package extension providing ForwardDiff-specific implementations for grid invariance (IND). +Activated automatically when ForwardDiff is loaded together with CTFlows. + +This extension adds: +- `deepvalue(x::ForwardDiff.Dual)` โ€” Recursive extraction of primal values from nested duals +- `real_norm(u::ForwardDiff.Dual, t)` โ€” Internal norm for scalar dual numbers + +These functions extend the fallback implementations in `CTFlows.Common` to support ForwardDiff dual numbers. +""" +module CTFlowsForwardDiff + +import DocStringExtensions: TYPEDSIGNATURES +import ForwardDiff + +using CTFlows: CTFlows +using CTFlows.Common: Common + +# ============================================================================= +# deepvalue and real_norm โ€” ForwardDiff-specific implementations +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Recursively extract the primal (real) value from a ForwardDiff dual number. + +Handles nested dual numbers for higher-order differentiation (e.g., second-order +derivatives where the partials themselves are dual numbers). This extends the +fallback `Common.deepvalue(x::Real)` to support ForwardDiff. + +# Arguments +- `x::ForwardDiff.Dual`: A ForwardDiff dual number (possibly nested). + +# Returns +- `Real`: The primal (Float64) part of the dual number. + +# Example +```julia +julia> using ForwardDiff + +julia> d1 = ForwardDiff.Dual{:Tag}(3.0, 1.0) +Dual{:Tag}(3.0, 1.0) + +julia> CTFlowsForwardDiff.deepvalue(d1) +3.0 + +julia> d2 = ForwardDiff.Dual{:Tag2}(d1, d1) # Nested dual +Dual{:Tag2}(Dual{:Tag}(3.0, 1.0), Dual{:Tag}(3.0, 1.0)) + +julia> CTFlowsForwardDiff.deepvalue(d2) +3.0 +``` + +See also: [`Common.deepvalue`](@ref), [`real_norm`](@ref) +""" +Common.deepvalue(x::ForwardDiff.Dual) = Common.deepvalue(ForwardDiff.value(x)) + +""" +$(TYPEDSIGNATURES) + +Compute the internal norm for a scalar ForwardDiff dual number using only its primal part. + +This extends the fallback `Common.real_norm(u::Real, t)` to support ForwardDiff dual numbers, +ensuring grid invariance (IND) when integrating ODEs with dual numbers. + +# Arguments +- `u::ForwardDiff.Dual`: A scalar dual number. +- `t`: Time parameter (unused but required by SciML interface). + +# Returns +- `Real`: The absolute value of the primal part. + +See also: [`Common.real_norm`](@ref), [`deepvalue`](@ref) +""" +Common.real_norm(u::ForwardDiff.Dual, t) = Common.real_norm(Common.deepvalue(u), t) + +end # module CTFlowsForwardDiff diff --git a/ext/CTFlowsOrdinaryDiffEqTsit5.jl b/ext/CTFlowsOrdinaryDiffEqTsit5.jl new file mode 100644 index 00000000..18f62650 --- /dev/null +++ b/ext/CTFlowsOrdinaryDiffEqTsit5.jl @@ -0,0 +1,33 @@ +""" +CTFlowsOrdinaryDiffEqTsit5 + +Package extension providing the default SciML ODE algorithm (Tsit5) via tag dispatch. +Activated automatically when OrdinaryDiffEqTsit5 is loaded together with CTFlows. +""" +module CTFlowsOrdinaryDiffEqTsit5 + +import DocStringExtensions: TYPEDSIGNATURES +import CTFlows.Integrators +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 + +""" +$(TYPEDSIGNATURES) + +Return the default SciML ODE algorithm (Tsit5) for Tsit5Tag type. + +Overrides the stub implementation in src/Integrators/sciml.jl that returns missing. + +# Returns +- `Tsit5`: The default Tsit5 algorithm. + +# Notes +- This function is called by the SciML metadata when Tsit5Tag type is used. +- Users can override this by explicitly passing the `alg` option. + +See also: [`CTFlows.Integrators.SciML`](@ref), [`CTFlows.Integrators.Tsit5Tag`](@ref). +""" +function Integrators.__default_sciml_algorithm(::Type{<:Integrators.Tsit5Tag}) + return Tsit5() +end + +end # module CTFlowsOrdinaryDiffEqTsit5 diff --git a/ext/CTFlowsPlots.jl b/ext/CTFlowsPlots.jl new file mode 100644 index 00000000..41ac0834 --- /dev/null +++ b/ext/CTFlowsPlots.jl @@ -0,0 +1,244 @@ +""" + CTFlowsPlots + +Package extension providing plotting capabilities for `VectorFieldSolution`. +Activated automatically when `Plots` is loaded together with `CTFlows`. +""" +module CTFlowsPlots + +import DocStringExtensions: TYPEDSIGNATURES + +using CTFlows: CTFlows +using CTFlows.Solutions: Solutions +using CTFlows.Integrators: Integrators +using Plots: Plots + +# ============================================================================= +# Default font settings for plots +# ============================================================================= + +const _PLOT_TITLE_FONT = Plots.font(10, Plots.default(:fontfamily)) +const _PLOT_LABEL_FONT_SIZE = 10 + +# ============================================================================= +# Plots.plot โ€” delegate to semantic accessors +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Internal helper to convert a solution to time and state arrays. + +# Arguments +- `sol::Solutions.VectorFieldSolution`: The solution to convert. + +# Returns +- `Tuple{AbstractVector, AbstractMatrix}`: A tuple of (time vector, state matrix). + +# Notes +- Uses `reduce(hcat, ...)'` for robust handling of 1D states. +- Uses `state(sol)` to explicitly obtain the state function. +- Internal function, not part of public API. +""" +function _sol_to_arrays(sol::Solutions.VectorFieldSolution) + ts = Integrators.times(sol) + x = Solutions.state(sol) + states = reduce(hcat, x.(ts))' + return ts, states +end + +""" +$(TYPEDSIGNATURES) + +Plot a `VectorFieldSolution` by extracting time points and states. + +Uses default `xlabel="time"` for the x-axis label and font size settings (10pt for labels and titles). + +# Arguments +- `sol::Solutions.VectorFieldSolution`: The solution to plot. +- `kwargs...`: Additional keyword arguments passed to `Plots.plot`. + +# Returns +- The plot object returned by `Plots.plot`. + +See also: [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref). +""" +function Plots.plot(sol::Solutions.VectorFieldSolution; kwargs...) + ts, states = _sol_to_arrays(sol) + return Plots.plot(ts, states; + xlabel="time", + xguidefontsize=_PLOT_LABEL_FONT_SIZE, + yguidefontsize=_PLOT_LABEL_FONT_SIZE, + titlefont=_PLOT_TITLE_FONT, + kwargs...) +end + +""" +$(TYPEDSIGNATURES) + +Plot into an existing plot by extracting time points and states. + +Uses default `xlabel="time"` for the x-axis label and font size settings (10pt for labels and titles). + +# Arguments +- `sol::Solutions.VectorFieldSolution`: The solution to plot. +- `kwargs...`: Additional keyword arguments passed to `Plots.plot!`. + +# Returns +- The modified plot object returned by `Plots.plot!`. + +See also: [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref). +""" +function Plots.plot!(sol::Solutions.VectorFieldSolution; kwargs...) + ts, states = _sol_to_arrays(sol) + return Plots.plot!(ts, states; + xlabel="time", + xguidefontsize=_PLOT_LABEL_FONT_SIZE, + yguidefontsize=_PLOT_LABEL_FONT_SIZE, + titlefont=_PLOT_TITLE_FONT, + kwargs...) +end + +""" +$(TYPEDSIGNATURES) + +Plot into an existing plot by extracting time points and states. + +Uses default `xlabel="time"` for the x-axis label and font size settings (10pt for labels and titles). + +# Arguments +- `p::Plots.Plot`: The existing plot to modify. +- `sol::Solutions.VectorFieldSolution`: The solution to plot. +- `kwargs...`: Additional keyword arguments passed to `Plots.plot!`. + +# Returns +- The modified plot object returned by `Plots.plot!`. + +See also: [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref). +""" +function Plots.plot!(p::Plots.Plot, sol::Solutions.VectorFieldSolution; kwargs...) + ts, states = _sol_to_arrays(sol) + return Plots.plot!(p, ts, states; + xlabel="time", + xguidefontsize=_PLOT_LABEL_FONT_SIZE, + yguidefontsize=_PLOT_LABEL_FONT_SIZE, + titlefont=_PLOT_TITLE_FONT, + kwargs...) +end + +# ============================================================================= +# Plots.plot โ€” HamiltonianVectorFieldSolution +# ============================================================================= + +""" +Internal helper to convert a Hamiltonian solution to time, state, and costate arrays. + +# Arguments +- `sol::Solutions.HamiltonianVectorFieldSolution`: The Hamiltonian solution to convert. + +# Returns +- `Tuple{AbstractVector, AbstractMatrix, AbstractMatrix}`: A tuple of (time vector, state matrix, costate matrix). + +# Notes +- Uses `reduce(hcat, ...)'` for robust handling of 1D states. +- Uses `state(sol)` and `costate(sol)` to explicitly obtain the state and costate functions. +- Internal function, not part of public API. +""" +function _ham_sol_to_arrays(sol::Solutions.HamiltonianVectorFieldSolution) + ts = Integrators.times(sol) + x = Solutions.state(sol) + p = Solutions.costate(sol) + states = reduce(hcat, x.(ts))' + costates = reduce(hcat, p.(ts))' + return ts, states, costates +end + +""" +$(TYPEDSIGNATURES) + +Plot a `HamiltonianVectorFieldSolution` by extracting time points, states, and costates. + +Uses a `(1, 2)` layout to show state and costate in separate subplots. +Uses default `xlabel=["time" "time"]` for both subplots, `title=["state" "costate"]` for subplot titles, and font size settings (10pt for labels and titles). + +# Arguments +- `sol::Solutions.HamiltonianVectorFieldSolution`: The Hamiltonian solution to plot. +- `kwargs...`: Additional keyword arguments passed to `Plots.plot`. + +# Returns +- The plot object returned by `Plots.plot`. + +See also: [`CTFlows.Solutions.HamiltonianVectorFieldSolution`](@ref), [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref), [`CTFlows.Solutions.costate`](@ref). +""" +function Plots.plot(sol::Solutions.HamiltonianVectorFieldSolution; kwargs...) + ts, states, costates = _ham_sol_to_arrays(sol) + return Plots.plot(ts, [states costates]; + layout=(1, 2), + xlabel=["time" "time"], + title=["state" "costate"], + xguidefontsize=_PLOT_LABEL_FONT_SIZE, + yguidefontsize=_PLOT_LABEL_FONT_SIZE, + titlefont=_PLOT_TITLE_FONT, + kwargs...) +end + +""" +$(TYPEDSIGNATURES) + +Plot into an existing plot by extracting time points, states, and costates. + +Uses a `(1, 2)` layout to show state and costate in separate subplots. +Uses default `xlabel=["time" "time"]` for both subplots, `title=["state" "costate"]` for subplot titles, and font size settings (10pt for labels and titles). + +# Arguments +- `sol::Solutions.HamiltonianVectorFieldSolution`: The Hamiltonian solution to plot. +- `kwargs...`: Additional keyword arguments passed to `Plots.plot!`. + +# Returns +- The modified plot object returned by `Plots.plot!`. + +See also: [`CTFlows.Solutions.HamiltonianVectorFieldSolution`](@ref), [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref), [`CTFlows.Solutions.costate`](@ref). +""" +function Plots.plot!(sol::Solutions.HamiltonianVectorFieldSolution; kwargs...) + ts, states, costates = _ham_sol_to_arrays(sol) + return Plots.plot!(ts, [states costates]; + layout=(1, 2), + xlabel=["time" "time"], + title=["state" "costate"], + xguidefontsize=_PLOT_LABEL_FONT_SIZE, + yguidefontsize=_PLOT_LABEL_FONT_SIZE, + titlefont=_PLOT_TITLE_FONT, + kwargs...) +end + +""" +$(TYPEDSIGNATURES) + +Plot into an existing plot by extracting time points, states, and costates. + +Uses a `(1, 2)` layout to show state and costate in separate subplots. +Uses default `xlabel=["time" "time"]` for both subplots, `title=["state" "costate"]` for subplot titles, and font size settings (10pt for labels and titles). + +# Arguments +- `p::Plots.Plot`: The existing plot to modify. +- `sol::Solutions.HamiltonianVectorFieldSolution`: The Hamiltonian solution to plot. +- `kwargs...`: Additional keyword arguments passed to `Plots.plot!`. + +# Returns +- The modified plot object returned by `Plots.plot!`. + +See also: [`CTFlows.Solutions.HamiltonianVectorFieldSolution`](@ref), [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref), [`CTFlows.Solutions.costate`](@ref). +""" +function Plots.plot!(p::Plots.Plot, sol::Solutions.HamiltonianVectorFieldSolution; kwargs...) + ts, states, costates = _ham_sol_to_arrays(sol) + return Plots.plot!(p, ts, [states costates]; + layout=(1, 2), + xlabel=["time" "time"], + title=["state" "costate"], + xguidefontsize=_PLOT_LABEL_FONT_SIZE, + yguidefontsize=_PLOT_LABEL_FONT_SIZE, + titlefont=_PLOT_TITLE_FONT, + kwargs...) +end + +end # module CTFlowsPlots diff --git a/ext/CTFlowsSciML/CTFlowsSciML.jl b/ext/CTFlowsSciML/CTFlowsSciML.jl new file mode 100644 index 00000000..d191999d --- /dev/null +++ b/ext/CTFlowsSciML/CTFlowsSciML.jl @@ -0,0 +1,48 @@ +""" + CTFlowsSciML + +Package extension providing the SciML implementation for `SciML`, +`SciMLFunctionSystem`, `SciMLProblemFlow`, and `ode_problem` for `VectorFieldSystem`. +Activated automatically when `DiffEqBase` and `SciMLBase` are loaded together with `CTFlows`. + +This extension provides: +- `real_norm` overload for grid invariance with ForwardDiff +- `Strategies.metadata` for SciML integrator options +- `SciMLFunctionSystem` โ€” wraps a `SciMLBase.AbstractODEFunction` as a CTFlows system +- `SciMLIntegrationResult` โ€” wraps `SciMLBase.AbstractODESolution` for CTFlows +- `Integrators.build_problem` for SciML integrators +- `Integrators.solve_problem` for SciML integrators +- `SciMLProblemFlow` โ€” wraps a `SciMLBase.AbstractODEProblem` as a CTFlows flow +- High-level `Flow(::AbstractODEFunction; ...)` and `Flow(::AbstractODEProblem; ...)` constructors +""" +module CTFlowsSciML + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +import CTSolvers.Strategies +import CTSolvers.Options + +using CTFlows: CTFlows +using CTFlows.Common: Common +using CTFlows.Configs: Configs +using CTFlows.Traits: Traits +using CTFlows.Systems: Systems +using CTFlows.Integrators: Integrators, SciML, SciMLTag, Tsit5Tag +using CTFlows.Flows: Flows, AbstractFlow, build_flow +using CTFlows.MultiPhase: MultiPhase +using DiffEqBase: DiffEqBase +using SciMLBase: SciMLBase, ODEProblem + +# ============================================================================= +# Include files in dependency order +# ============================================================================= + +include("real_norm.jl") +include("strategies.jl") +include("sciml_function_system.jl") +include("integration_result.jl") +include("build_and_solve.jl") +include("problem_flow.jl") +include("flow_constructors.jl") + +end # module CTFlowsSciML diff --git a/ext/CTFlowsSciML/build_and_solve.jl b/ext/CTFlowsSciML/build_and_solve.jl new file mode 100644 index 00000000..4ef54612 --- /dev/null +++ b/ext/CTFlowsSciML/build_and_solve.jl @@ -0,0 +1,225 @@ +# ============================================================================= +# _check_retcode โ€” private helper +# ============================================================================= + +""" + _check_retcode(sol, unsafe) + +Check the return code of a SciML ODE solution and throw `SolverFailure` if integration failed. + +# Arguments +- `sol`: A SciML ODE solution with a `retcode` field. +- `unsafe::Bool`: If `true`, bypass retcode checking; if `false`, throw on failure. + +# Throws +- `CTBase.Exceptions.SolverFailure`: If `!unsafe` and the retcode indicates failure. +""" +function _check_retcode(sol, unsafe) + if !unsafe && !SciMLBase.successful_retcode(sol.retcode) + throw(Exceptions.SolverFailure( + "ODE integration failed"; + retcode = string(sol.retcode), + suggestion = "Try tightening tolerances (reltol, abstol) or changing the solver algorithm.", + context = "SciML solve_problem", + )) + end +end + +# ============================================================================= +# SciML problem building โ€” actual implementation (generic) +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Build an `ODEProblem` from a system and configuration. + +Dispatches between in-place and out-of-place RHS based on the mutability of the initial condition: +- If `ismutable(u0)` is true, uses the in-place `rhs(system)` with signature `(du, u, p, t) -> nothing`. +- If `ismutable(u0)` is false (e.g., `StaticArrays.SVector`), uses the out-of-place `rhs_oop(system)` with signature `(u, p, t) -> du`. + +This allows zero-allocation integration with immutable array types like `StaticArrays.SVector`. + +# Arguments +- `integ::SciML`: The SciML integrator strategy. +- `system::Systems.AbstractSystem`: The system to build an ODE problem for. +- `config::Configs.AbstractConfig`: The configuration containing initial condition and time span. +- `variable`: Optional variable parameter for non-fixed systems. + +# Returns +- `SciMLBase.ODEProblem`: The ODE problem ready for integration. + +# Notes +- The variable parameter is wrapped in `Common.ODEParameters` for uniform access. +- For Hamiltonian systems, the initial condition concatenates `x0` and `p0`. +- Checks for unsupported combinations (e.g., InPlace VectorField with scalar u0). + +See also: [`CTFlows.Systems.rhs`](@ref), [`CTFlows.Common.ODEParameters`](@ref). +""" +function Integrators.build_problem( + integ::SciML, + system::Systems.AbstractSystem, + config::Configs.AbstractConfig; + variable, + cache, + ) + u0 = Configs.initial_condition(config) + Systems._check_vf_scalar_inplace(system, u0) # guard for InPlace VF + scalar + p = Common.ODEParameters(variable, cache) + if ismutable(u0) + f! = Systems.rhs(system) + prob = ODEProblem(f!, u0, Configs.tspan(config), p) + else + f = Systems.rhs_oop(system, false) # false = is_u0_mutable + prob = ODEProblem(f, u0, Configs.tspan(config), p) + end + return prob +end + +""" +$(TYPEDSIGNATURES) + +Build an `ODEProblem` for HamiltonianVectorFieldSystem with Hamiltonian configurations. + +Constructs lazy RHS closures based on the actual initial condition shapes. + +# Arguments +- `integ::SciML`: The SciML integrator strategy. +- `system::Systems.HamiltonianVectorFieldSystem`: The Hamiltonian vector field system. +- `config::Configs.AbstractHamiltonianConfig`: The Hamiltonian configuration (non-augmented). +- `variable`: Variable parameter for the system. +- `cache`: Cache for automatic differentiation. + +# Returns +- `SciMLBase.ODEProblem`: The ODE problem with lazy-built RHS. + +See also: [`CTFlows.Systems.build_rhs`](@ref), [`CTFlows.Systems.build_oop_rhs`](@ref). +""" +function Integrators.build_problem( + integ::SciML, + system::Systems.HamiltonianVectorFieldSystem, + config::Configs.AbstractHamiltonianConfig; + variable, + cache, +) + x0 = Configs.initial_state(config) + p0 = Configs.initial_costate(config) + u0 = Configs.initial_condition(config) + ฮป = Common.ODEParameters(variable, cache) + if ismutable(u0) + f! = Systems.build_rhs(system, x0, p0) + prob = ODEProblem(f!, u0, Configs.tspan(config), ฮป) + else + f = Systems.build_oop_rhs(system, x0, p0) + prob = ODEProblem(f, u0, Configs.tspan(config), ฮป) + end + return prob +end + +""" +$(TYPEDSIGNATURES) + +Build an `ODEProblem` for HamiltonianSystem with Hamiltonian configurations. + +Constructs lazy RHS closures based on the actual initial condition shapes. + +# Arguments +- `integ::SciML`: The SciML integrator strategy. +- `system::Systems.HamiltonianSystem`: The Hamiltonian system (AD-based). +- `config::Configs.AbstractHamiltonianConfig`: The Hamiltonian configuration (non-augmented). +- `variable`: Variable parameter for the system. +- `cache`: Cache for automatic differentiation. + +# Returns +- `SciMLBase.ODEProblem`: The ODE problem with lazy-built RHS. + +See also: [`CTFlows.Systems.build_rhs`](@ref), [`CTFlows.Systems.build_oop_rhs`](@ref). +""" +function Integrators.build_problem( + integ::SciML, + system::Systems.HamiltonianSystem, + config::Configs.AbstractHamiltonianConfig; + variable, + cache, +) + x0 = Configs.initial_state(config) + p0 = Configs.initial_costate(config) + u0 = Configs.initial_condition(config) + ฮป = Common.ODEParameters(variable, cache) + if ismutable(u0) + f! = Systems.build_rhs(system, x0, p0) + prob = ODEProblem(f!, u0, Configs.tspan(config), ฮป) + else + f = Systems.build_oop_rhs(system, x0, p0) + prob = ODEProblem(f, u0, Configs.tspan(config), ฮป) + end + return prob +end + +""" +$(TYPEDSIGNATURES) + +Build an `ODEProblem` for augmented Hamiltonian systems. + +Specialized overload for `HamiltonianSystem` with `AbstractAugmentedHamiltonianConfig`. +Uses the augmented RHS that computes state, costate, and variable costate derivatives. + +# Arguments +- `integ::SciML`: The SciML integrator strategy. +- `system::Systems.HamiltonianSystem`: The Hamiltonian system. +- `config::Configs.AbstractAugmentedHamiltonianConfig`: The augmented Hamiltonian configuration. +- `variable`: Variable parameter for the augmented system. +- `cache`: Cache for automatic differentiation. + +# Returns +- `SciMLBase.ODEProblem`: The ODE problem with augmented RHS. + +# Notes +- Only in-place implementation (mutable arrays) since `pv0 = zeros(...)` guarantees mutability. +- TODO: Add out-of-place path for SVector support in the future. +- Uses `Systems.build_rhs_augmented` to construct the augmented RHS function. + +See also: [`CTFlows.Systems.build_rhs_augmented`](@ref), [`CTFlows.Configs.AbstractAugmentedHamiltonianConfig`](@ref). +""" +function Integrators.build_problem( + integ::SciML, + system::Systems.HamiltonianSystem, + config::Configs.AbstractAugmentedHamiltonianConfig; + variable, + cache, +) + u0 = Configs.initial_condition(config) # vcat(x0, p0, pv0) + ฮป = Common.ODEParameters(variable, cache) + n_x = length(Configs.initial_state(config)) + n_v = length(Configs.initial_variable_costate(config)) + f! = Systems.build_rhs_augmented(system, n_x, n_v) + return ODEProblem(f!, u0, Configs.tspan(config), ฮป) +end + +# ============================================================================= +# SciML solve โ€” actual implementation (generic) +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Solve an `ODEProblem` using resolved options. +Returns a `SciMLIntegrationResult` wrapping the raw `ODESolution`. + +# Arguments +- `integ::SciML`: The SciML integrator strategy. +- `prob::SciMLBase.AbstractODEProblem`: The ODE problem to solve. +- `options::Dict{Symbol,Any}`: Resolved solver options (typically from `build_options`). +- `unsafe=Common.__unsafe()`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- `SciMLIntegrationResult`: The integration result wrapping the SciML ODE solution. + +# Throws +- `CTBase.Exceptions.SolverFailure`: If the ODE solver returns an unsuccessful retcode and `unsafe=false`. +""" +function Integrators.solve_problem(integ::SciML, prob::SciMLBase.AbstractODEProblem, options::Dict{Symbol,<:Any}; unsafe=Common.__unsafe()) + ode_sol = SciMLBase.solve(prob; options...) + _check_retcode(ode_sol, unsafe) + return SciMLIntegrationResult(ode_sol) +end diff --git a/ext/CTFlowsSciML/flow_constructors.jl b/ext/CTFlowsSciML/flow_constructors.jl new file mode 100644 index 00000000..e1fcde1f --- /dev/null +++ b/ext/CTFlowsSciML/flow_constructors.jl @@ -0,0 +1,68 @@ +# ============================================================================= +# High-level Flow constructors +# ============================================================================= + +""" + Flow(f::SciMLBase.AbstractODEFunction; opts...) + +Create a `Flow` from a SciML ODE function. + +Delegates to the standard CTFlows pipeline: +1. Wraps `f` in a `SciMLFunctionSystem` +2. Builds a `SciML` integrator from `kwargs` +3. Constructs a `StateFlow` via `build_flow` + +# Arguments +- `f::SciMLBase.AbstractODEFunction`: The ODE function to wrap. +- `opts...`: Passed to the `SciML` integrator constructor (e.g., `reltol=1e-10`). + +# Returns +- `StateFlow`: The flow for the given function. + +# Example +```julia +using SciMLBase, CTFlows + +f = ODEFunction((du, u, p, t) -> du .= -p .* u) +flow = Flow(f; reltol=1e-10) +xf = flow(0.0, [1.0], 1.0; variable=2.0) +``` +""" +function Flows.Flow(f::SciMLBase.AbstractODEFunction; opts...) + sys = SciMLFunctionSystem(f) + integ = Integrators.build_integrator(; opts...) + return build_flow(sys, integ) +end + +""" + Flow(prob::SciMLBase.AbstractODEProblem; opts...) + +Create a `Flow` from a SciML ODE problem. + +Constructs a `SciMLProblemFlow` directly, which wraps the problem and a `SciML` integrator. +This bypasses the standard CTFlows system-building pipeline since the problem is already +fully assembled (includes u0, tspan, and p). + +# Arguments +- `prob::SciMLBase.AbstractODEProblem`: The ODE problem to wrap. +- `opts...`: Passed to the `SciML` integrator constructor (e.g., `reltol=1e-10`). + +# Returns +- `SciMLProblemFlow`: The flow for the given problem. + +# Example +```julia +using SciMLBase, CTFlows + +prob = ODEProblem((du, u, p, t) -> du .= -p .* u, [1.0], (0.0, 1.0), 2.0) +flow = Flow(prob; reltol=1e-10) +result = flow(; unsafe=false) # No-arg call: solve as-is +xf = Integrators.final_state(result) +result = flow(0.5, [2.0], 2.0; variable=3.0, unsafe=false) # Remake call +xf = Integrators.final_state(result) +``` +""" +function Flows.Flow(prob::SciMLBase.AbstractODEProblem; opts...) + integ = Integrators.build_integrator(; opts...) + return SciMLProblemFlow(prob, integ) +end diff --git a/ext/CTFlowsSciML/integration_result.jl b/ext/CTFlowsSciML/integration_result.jl new file mode 100644 index 00000000..6ef4ac13 --- /dev/null +++ b/ext/CTFlowsSciML/integration_result.jl @@ -0,0 +1,97 @@ +# ============================================================================= +# SciMLIntegrationResult +# ============================================================================= + +""" +$(TYPEDEF) + +Integration result from a SciML solver. + +Wraps a `SciMLBase.AbstractODESolution` and implements the `AbstractIntegrationResult` +interface required by the Solutions layer. + +# Fields +- `ode_sol::S`: The raw SciML ODE solution. +""" +struct SciMLIntegrationResult{S<:SciMLBase.AbstractODESolution} <: Integrators.AbstractIntegrationResult + ode_sol::S +end + +""" +$(TYPEDSIGNATURES) + +Return the final state vector from the SciML ODE solution. +""" +Integrators.final_state(r::SciMLIntegrationResult) = last(r.ode_sol.u) + +""" +$(TYPEDSIGNATURES) + +Return the vector of time points from the SciML ODE solution. +""" +Integrators.times(r::SciMLIntegrationResult) = r.ode_sol.t + +""" +$(TYPEDSIGNATURES) + +Evaluate the SciML ODE solution at a specific time `t` using its interpolation. +""" +Integrators.evaluate_at(r::SciMLIntegrationResult, t::Real) = r.ode_sol(t) + +# ============================================================================= +# SciML Segment Merging +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Merge a sequence of SciML ODE solutions into a single solution. +This allows concatenation of trajectories from multiple phases. +""" +function Integrators.merge(segments::AbstractVector{<:SciMLIntegrationResult}) + # Extract the raw ODESolution objects + ode_sols = [r.ode_sol for r in segments] + + if isempty(ode_sols) + throw(Exceptions.IncorrectArgument( + "Cannot merge empty sequence of segments"; + got = "0 segments", + expected = "at least 1 segment", + context = "SciML merge", + )) + end + + if length(ode_sols) == 1 + return segments[1] + end + + # Merge using DiffEqBase.EnsembleSolution (or custom concatenation if we want a single ODESolution) + # The simplest robust way in SciML to represent concatenated trajectories over time + # is often to reconstruct an ODESolution, but since times are strictly monotonic + # (except at jumps where they might be equal), we can concatenate `t`, `u` and `k`. + # Let's concatenate them to form a continuous trajectory. + + t_merged = copy(ode_sols[1].t) + u_merged = copy(ode_sols[1].u) + + for i in eachindex(ode_sols)[2:end] + sol = ode_sols[i] + # Append all points except the first one (which is the same time as previous last, but after jump) + # Actually, keep it so we can have discontinuities properly represented. + append!(t_merged, sol.t) + append!(u_merged, sol.u) + end + + # Create a new ODESolution (using the first one as template) + # This is a bit of a hack, but SciML doesn't provide a clean `vcat` for ODESolutions yet. + # Note: Interpolation might not work properly across the whole merged solution this way. + sol1 = ode_sols[1] + + merged_sol = DiffEqBase.build_solution( + sol1.prob, sol1.alg, t_merged, u_merged; + retcode = SciMLBase.ReturnCode.Success, + dense = false # Disable dense interpolation for merged as it requires k-arrays which are tricky to merge + ) + + return SciMLIntegrationResult(merged_sol) +end diff --git a/ext/CTFlowsSciML/problem_flow.jl b/ext/CTFlowsSciML/problem_flow.jl new file mode 100644 index 00000000..fd02f9f7 --- /dev/null +++ b/ext/CTFlowsSciML/problem_flow.jl @@ -0,0 +1,140 @@ +# ============================================================================= +# SciMLProblemFlow +# ============================================================================= + +""" +$(TYPEDEF) + +Concrete `AbstractFlow` wrapping a `SciMLBase.AbstractODEProblem` directly. + +Unlike `StateFlow` which wraps an `AbstractSystem` and an `AbstractIntegrator`, +`SciMLProblemFlow` wraps a fully-assembled ODE problem. The `system` method returns +`nothing` because there is no CTFlows `AbstractSystem` to extract. + +The flow supports three call modes: +- **No-arg call** `f(; unsafe)` โ€” solves the problem as-is with trajectory options. +- **Point call** `f(t0, x0, tf; variable, unsafe)` โ€” calls `SciMLBase.remake` first with point options, returns final state only. +- **Trajectory call** `f(tspan, x0; variable, unsafe)` โ€” calls `SciMLBase.remake` first with trajectory options, returns complete solution. + +# Type Parameters +- `P <: SciMLBase.AbstractODEProblem`: The wrapped ODE problem. +- `I <: Integrators.AbstractIntegrator`: The integrator strategy (typically `SciML`). + +# Fields +- `prob::P`: The wrapped ODE problem (contains u0, tspan, p). +- `integrator::I`: The integrator strategy (provides options via `build_options`). + +# Example +```julia +using SciMLBase, CTFlows + +prob = ODEProblem((du, u, p, t) -> du .= -p .* u, [1.0], (0.0, 1.0), 2.0) +flow = SciMLProblemFlow(prob, Integrators.SciML()) + +# No-arg call: solve as-is +sol = flow(; unsafe=false) + +# Point call: modify initial condition and time span, returns final state +xf = flow(0.5, [2.0], 2.0; variable=3.0, unsafe=false) + +# Trajectory call: modify initial condition and time span, returns complete solution +sol = flow((0.5, 2.0), [2.0]; variable=3.0, unsafe=false) +``` +""" +struct SciMLProblemFlow{ + P <: SciMLBase.AbstractODEProblem, + I <: Integrators.AbstractIntegrator +} <: AbstractFlow{Traits.NonAutonomous, Traits.NonFixed} + prob::P + integrator::I +end + +Flows.system(f::SciMLProblemFlow) = nothing +Flows.integrator(f::SciMLProblemFlow) = f.integrator + +# No-arg call: solve problem as-is with trajectory options +function (f::SciMLProblemFlow)(; unsafe = Common.__unsafe()) + opts = Integrators.build_options(f.integrator, nothing) + sol = SciMLBase.solve(f.prob; opts...) + _check_retcode(sol, unsafe) + return SciMLIntegrationResult(sol) +end + +# Remake call: modify initial condition, time span, and optionally parameter +function (f::SciMLProblemFlow)( + t0::Real, + x0, + tf::Real; + variable = Common.__variable(), + unsafe = Common.__unsafe(), +) + kw = (; u0 = x0, tspan = (t0, tf)) + if !(variable isa Common.NotProvided) + kw = merge(kw, (; p = variable)) + end + prob = SciMLBase.remake(f.prob; kw...) + config = Configs.StatePointConfig(t0, x0, tf) + opts = Integrators.build_options(f.integrator, config) + sol = SciMLBase.solve(prob; opts...) + _check_retcode(sol, unsafe) + result = SciMLIntegrationResult(sol) + return Integrators.final_state(result) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `SciMLProblemFlow` with trajectory configuration. + +Builds a `StateTrajectoryConfig` internally and returns the complete solution. + +# Arguments +- `f::SciMLProblemFlow`: The SciML problem flow to solve. +- `tspan::Tuple{Real, Real}`: Time span as a tuple (t0, tf). +- `x0`: Initial state vector. +- `variable`: The variable parameter value (optional, passed to remake). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- `SciMLIntegrationResult`: The complete integration result with trajectory data. + +# Example +```julia +prob = ODEProblem((du, u, p, t) -> du .= -p .* u, [1.0], (0.0, 1.0), 2.0) +flow = SciMLProblemFlow(prob, Integrators.SciML()) + +# Trajectory call: get full solution +sol = flow((0.0, 1.0), [1.0]) +``` +""" +function (f::SciMLProblemFlow)( + tspan::Tuple{Real, Real}, + x0; + variable = Common.__variable(), + unsafe = Common.__unsafe(), +) + kw = (; u0 = x0, tspan = tspan) + if !(variable isa Common.NotProvided) + kw = merge(kw, (; p = variable)) + end + prob = SciMLBase.remake(f.prob; kw...) + config = Configs.StateTrajectoryConfig(tspan, x0) + opts = Integrators.build_options(f.integrator, config) + sol = SciMLBase.solve(prob; opts...) + _check_retcode(sol, unsafe) + return SciMLIntegrationResult(sol) +end + +function Base.show(io::IO, ::MIME"text/plain", f::SciMLProblemFlow) + println(io, "SciMLProblemFlow") + println(io, " tspan: ", f.prob.tspan) + println(io, " u0: ", f.prob.u0) + print(io, " integrator: ") + show(io, f.integrator) + println(io) + Flows._print_user_options(io, f.integrator) +end + +function Base.show(io::IO, f::SciMLProblemFlow) + print(io, "SciMLProblemFlow(tspan=", f.prob.tspan, ")") +end diff --git a/ext/CTFlowsSciML/real_norm.jl b/ext/CTFlowsSciML/real_norm.jl new file mode 100644 index 00000000..ce784181 --- /dev/null +++ b/ext/CTFlowsSciML/real_norm.jl @@ -0,0 +1,40 @@ +# ============================================================================= +# real_norm overload for SciML +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Compute the internal norm for adaptive step-size control using only the primal +parts of dual numbers. + +This function ensures grid invariance (IND) when integrating ODEs with ForwardDiff +dual numbers: the adaptive time grid chosen by the solver is identical whether +integrating with real numbers or dual numbers. Without this, the step-size +controller would make decisions based on dual values, breaking grid invariance. + +This implementation uses `Common.deepvalue` to extract primal parts and +`DiffEqBase.ODE_DEFAULT_NORM` to compute the norm. When ForwardDiff is loaded, +`Common.deepvalue` is extended to handle dual numbers via `CTFlowsForwardDiff`. + +# Arguments +- `u::AbstractArray`: State vector (may contain dual numbers). +- `t`: Time parameter (unused but required by SciML interface). + +# Returns +- `Real`: The norm computed on the primal parts only. + +# Example +```julia +julia> using ForwardDiff + +julia> u_real = [1.0, 2.0, 3.0] +julia> u_dual = ForwardDiff.Dual{:T}.(u_real, ones(3)) + +julia> CTFlowsSciML.real_norm(u_real, 0.0) โ‰ˆ CTFlowsSciML.real_norm(u_dual, 0.0) +true +``` + +See also: [`Common.deepvalue`](@ref), [`Common.real_norm`](@ref) +""" +Common.real_norm(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(Common.deepvalue.(u), t) diff --git a/ext/CTFlowsSciML/sciml_function_system.jl b/ext/CTFlowsSciML/sciml_function_system.jl new file mode 100644 index 00000000..13097015 --- /dev/null +++ b/ext/CTFlowsSciML/sciml_function_system.jl @@ -0,0 +1,216 @@ +# ============================================================================= +# SciMLFunctionSystem +# ============================================================================= + +""" +$(TYPEDEF) + +Concrete `AbstractStateSystem` wrapping a `SciMLBase.AbstractODEFunction`. + +Unlike CTFlows-native systems (`VectorFieldSystem`), this system passes `p = variable` +directly to the ODE โ€” no `ODEParameters` wrapper โ€” so users can pass arbitrary +SciML parameter objects. + +The mutability trait is encoded in the `iip` type parameter of the wrapped function: +- `AbstractODEFunction{true}` โ†’ in-place `f!(du, u, p, t)` +- `AbstractODEFunction{false}` โ†’ out-of-place `f(u, p, t) -> du` + +The system pre-computes cross-adapters for both in-place and out-of-place call modes, +similar to `VectorFieldSystem`, ensuring compatibility with the generic `build_problem` +which dispatches based on `u0` mutability. + +# Type Parameters +- `F <: SciMLBase.AbstractODEFunction`: The wrapped ODE function. +- `RHS<:Function`: Pre-computed in-place RHS closure. +- `OOPROHS<:Function`: Pre-computed out-of-place RHS closure. +- `FINRHS`: Finalize closure for in-place functions with immutable `u0`, or `Nothing`. + +# Fields +- `f::F`: The wrapped SciML ODE function. +- `rhs_fn::RHS`: In-place RHS with signature `(du, u, ฮป, t)`. +- `rhs_oop_fn::OOPROHS`: Out-of-place RHS with signature `(u, ฮป, t)`. +- `rhs_oop_finalize_fn::FINRHS`: Out-of-place RHS for immutable `u0` (iip only), or `Nothing`. + +# Example +```julia +using SciMLBase, CTFlows + +f = ODEFunction((du, u, p, t) -> du .= -p .* u) +sys = SciMLFunctionSystem(f) +# sys.rhs_fn is pre-computed in-place closure +# sys.rhs_oop_fn is pre-computed out-of-place closure (allocates buffer) +``` +""" +struct SciMLFunctionSystem{ + F <: SciMLBase.AbstractODEFunction, + RHS<:Function, + OOPROHS<:Function, + FINRHS +} <: Systems.AbstractStateSystem{Traits.NonAutonomous, Traits.NonFixed} + f::F + rhs_fn::RHS + rhs_oop_fn::OOPROHS + rhs_oop_finalize_fn::FINRHS +end + +# ============================================================================= +# Constructors +# ============================================================================= + +function SciMLFunctionSystem(f::SciMLBase.AbstractODEFunction{true}) + # In-place function: f!(du, u, p, t) + rhs_fn = (du, u, ฮป, t) -> (f(du, u, Common.variable(ฮป), t); nothing) + + # Out-of-place wrapper: allocates buffer + rhs_oop_fn = (u, ฮป, t) -> begin + dx = similar(u) + f(dx, u, Common.variable(ฮป), t) + return dx + end + + # Out-of-place finalize for immutable u0 + rhs_oop_finalize_fn = (u, ฮป, t) -> begin + dx = similar(u) + f(dx, u, Common.variable(ฮป), t) + return typeof(u)(dx) + end + + return SciMLFunctionSystem{typeof(f), typeof(rhs_fn), typeof(rhs_oop_fn), typeof(rhs_oop_finalize_fn)}( + f, rhs_fn, rhs_oop_fn, rhs_oop_finalize_fn + ) +end + +function SciMLFunctionSystem(f::SciMLBase.AbstractODEFunction{false}) + # Out-of-place function: f(u, p, t) -> du + rhs_fn = (du, u, ฮป, t) -> (du .= f(u, Common.variable(ฮป), t); nothing) + + # Out-of-place direct + rhs_oop_fn = (u, ฮป, t) -> f(u, Common.variable(ฮป), t) + + # No finalize needed for out-of-place + rhs_oop_finalize_fn = nothing + + return SciMLFunctionSystem{typeof(f), typeof(rhs_fn), typeof(rhs_oop_fn), Nothing}( + f, rhs_fn, rhs_oop_fn, rhs_oop_finalize_fn + ) +end + +# ============================================================================= +# rhs and rhs_oop methods +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +In-place right-hand side for a `SciMLFunctionSystem`. + +Returns the pre-computed in-place closure stored in the system, which has signature +`(du, u, ฮป, t)` where `ฮป` is a `Common.ODEParameters` wrapper. The closure extracts +`Common.variable(ฮป)` and calls the underlying SciML function. + +# Arguments +- `sys::SciMLFunctionSystem`: The system for which to return the RHS function. + +# Returns +- `Function`: The pre-computed closure with signature `(du, u, ฮป, t)`. + +See also: [`SciMLFunctionSystem`](@ref), [`Systems.rhs_oop`](@ref). +""" +Systems.rhs(sys::SciMLFunctionSystem) = sys.rhs_fn + +""" +$(TYPEDSIGNATURES) + +Out-of-place right-hand side for a `SciMLFunctionSystem` with out-of-place function. + +Returns the pre-computed out-of-place closure with signature `(u, ฮป, t)`. The optional +`is_mutable` argument is accepted but ignored: for out-of-place systems `rhs_oop` is +always the correct callable regardless of u0 mutability. + +# Arguments +- `sys::SciMLFunctionSystem{..., Nothing}`: The out-of-place system. +- `::Bool`: Ignored. Accepted for API uniformity. + +# Returns +- `Function`: The pre-computed closure with signature `(u, ฮป, t)`. + +See also: [`SciMLFunctionSystem`](@ref), [`Systems.rhs`](@ref). +""" +function Systems.rhs_oop(sys::SciMLFunctionSystem{F, RHS, OOPROHS, Nothing}, ::Bool = true) where {F, RHS, OOPROHS} + return sys.rhs_oop_fn +end + +""" +$(TYPEDSIGNATURES) + +Out-of-place right-hand side for a `SciMLFunctionSystem` with in-place function, +dispatching on u0 mutability. + +For in-place SciML functions the appropriate callable depends on whether the initial +condition `u0` is mutable: +- **`is_mutable = true`** (default): returns `rhs_oop_fn`, which allocates a mutable + buffer and returns it. Correct when `u0` is a `Vector` or similar mutable array. +- **`is_mutable = false`**: returns `rhs_oop_finalize_fn`, which allocates a mutable + buffer, fills it via the in-place call, then converts back to `typeof(u)`. A one-time + performance warning is emitted in this case. + +# Arguments +- `sys::SciMLFunctionSystem{..., FINRHS}`: The in-place system. +- `is_mutable::Bool`: `true` if u0 is mutable, `false` if immutable. Defaults to `true`. + +# Returns +- `Function`: The appropriate closure with signature `(u, ฮป, t)`. + +# Notes +- Prefer out-of-place SciML functions when u0 is immutable (e.g. `StaticArrays.SVector`) + for best performance. + +See also: [`SciMLFunctionSystem`](@ref), [`Systems.rhs`](@ref). +""" +function Systems.rhs_oop(sys::SciMLFunctionSystem{F, RHS, OOPROHS, FINRHS}, is_mutable::Bool = true) where {F, RHS, OOPROHS, FINRHS} + is_mutable && return sys.rhs_oop_fn + @warn "InPlace SciMLFunction with immutable u0 (e.g. SVector): consider using an out-of-place function for better performance." + return sys.rhs_oop_finalize_fn +end + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a `SciMLFunctionSystem`. + +Shows the type name, the wrapped ODE function type, and its mutability trait. + +# Arguments +- `io::IO`: The IO stream to write to. +- `sys::SciMLFunctionSystem`: The system to display. + +See also: [`SciMLFunctionSystem`](@ref). +""" +function Base.show(io::IO, sys::SciMLFunctionSystem{F}) where F + println(io, "SciMLFunctionSystem") + iip = SciMLBase.isinplace(sys.f) + mut = iip ? "in-place" : "out-of-place" + print(io, " wraps: ODEFunction: non-autonomous, variable, ", mut) +end + +""" +$(TYPEDSIGNATURES) + +Display a `SciMLFunctionSystem` in the REPL with text/plain MIME type. + +Delegates to the compact show method. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `sys::SciMLFunctionSystem`: The system to display. + +See also: [`SciMLFunctionSystem`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", sys::SciMLFunctionSystem) + show(io, sys) +end diff --git a/ext/CTFlowsSciML/strategies.jl b/ext/CTFlowsSciML/strategies.jl new file mode 100644 index 00000000..301712cf --- /dev/null +++ b/ext/CTFlowsSciML/strategies.jl @@ -0,0 +1,378 @@ +# ============================================================================= +# Strategies.metadata โ€” option definitions for SciML +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return metadata defining `SciML` options and their specifications. + +The `internalnorm` option defaults to `real_norm`, which extracts the primal (Float64) +part of ForwardDiff dual numbers to ensure grid invariance (IND) when ForwardDiff is loaded. +""" +function Strategies.metadata(::Type{SciML}) + return Strategies.StrategyMetadata( + Strategies.OptionDefinition(; + name = :alg, + type = SciMLBase.AbstractDEAlgorithm, + default = Integrators.__default_sciml_algorithm(Integrators.Tsit5Tag), + description = "ODE algorithm (e.g. Tsit5(), Vern6()).", + aliases = (:algorithm, :solver), + ), + Strategies.OptionDefinition(; + name = :reltol, + type = Real, + default = 1e-8, + description = "Relative tolerance for the ODE solver.", + aliases = (:rtol, :rel_tol), + validator = x -> + x > 0 || throw( + Exceptions.IncorrectArgument( + "Invalid reltol value"; + got = "reltol=$x", + expected = "positive real number (> 0)", + suggestion = "Provide a positive tolerance (e.g., 1e-8, 1e-10).", + context = "SciML reltol validation", + ), + ), + ), + Strategies.OptionDefinition(; + name = :abstol, + type = Real, + default = 1e-8, + description = "Absolute tolerance for the ODE solver.", + aliases = (:atol, :abs_tol), + validator = x -> + x > 0 || throw( + Exceptions.IncorrectArgument( + "Invalid abstol value"; + got = "abstol=$x", + expected = "positive real number (> 0)", + suggestion = "Provide a positive tolerance (e.g., 1e-8, 1e-10).", + context = "SciML abstol validation", + ), + ), + ), + Strategies.OptionDefinition(; + name = :maxiters, + type = Integer, + default = Options.NotProvided, + description = "Maximum number of solver iterations.", + aliases=(:max_iters, :max_iter, :maxiter, :max_iterations, :maxit), + validator = x -> + x > 0 || throw( + Exceptions.IncorrectArgument( + "Invalid maxiters value"; + got = "maxiters=$x", + expected = "positive integer (> 0)", + suggestion = "Provide a positive iteration count (e.g., 10^5).", + context = "SciML maxiters validation", + ), + ), + ), + Strategies.OptionDefinition(; + name = :dt, + type = Real, + default = Options.NotProvided, + description = "Fixed step size (used when adaptive=false).", + aliases = (:dt0, :timestep), + validator = x -> + x > 0 || throw( + Exceptions.IncorrectArgument( + "Invalid dt value"; + got = "dt=$x", + expected = "positive real number (> 0)", + suggestion = "Provide a positive step size (e.g., 0.01).", + context = "SciML dt validation", + ), + ), + ), + Strategies.OptionDefinition(; + name = :adaptive, + type = Bool, + default = Options.NotProvided, + description = "Whether to use adaptive step-size control.", + aliases = (:adaptive_step, :adaptive_stepping), + ), + Strategies.OptionDefinition(; + name = :save_everystep, + type = Union{Bool, Symbol}, + default = :auto, + description = "Save the solution at every solver step. Set `true`/`false` to force, or `:auto` to infer from call pattern (false for `flow(t0, x0[, p0], tf)`, true for `flow((t0, tf), x0[, p0])`).", + ), + Strategies.OptionDefinition(; + name = :saveat, + type = Union{Real, AbstractVector}, + default = Options.NotProvided, + description = "Times at which to save the solution (Vector or range).", + aliases = (:save_at, :save_times), + ), + Strategies.OptionDefinition(; + name = :dense, + type = Union{Bool, Symbol}, + default = :auto, + description = "Dense output. Set `true`/`false` to force, or `:auto` to infer from call pattern (false for `flow(t0, x0[, p0], tf)`, true for `flow((t0, tf), x0[, p0])`).", + ), + Strategies.OptionDefinition(; + name = :save_idxs, + type = AbstractVector{<:Integer}, + default = Options.NotProvided, + description = "Indices of components to save (Vector of integers).", + aliases = (:saveindices, :save_indices), + ), + Strategies.OptionDefinition(; + name = :tstops, + type = AbstractVector{<:Real}, + default = Options.NotProvided, + description = "Extra times the solver must step to (for discontinuities).", + aliases = (:t_stops, :stop_times), + ), + Strategies.OptionDefinition(; + name = :d_discontinuities, + type = AbstractVector{<:Real}, + default = Options.NotProvided, + description = "Locations of discontinuities in low-order derivatives.", + ), + Strategies.OptionDefinition(; + name = :dtmax, + type = Real, + default = Options.NotProvided, + description = "Maximum step size for adaptive timestepping.", + aliases = (:max_dt, :dt_max), + validator = x -> + x > 0 || throw( + Exceptions.IncorrectArgument( + "Invalid dtmax value"; + got = "dtmax=$x", + expected = "positive real number (> 0)", + suggestion = "Provide a positive maximum step size (e.g., 0.1).", + context = "SciML dtmax validation", + ), + ), + ), + Strategies.OptionDefinition(; + name = :dtmin, + type = Real, + default = Options.NotProvided, + description = "Minimum step size for adaptive timestepping.", + aliases = (:min_dt, :dt_min), + validator = x -> + x > 0 || throw( + Exceptions.IncorrectArgument( + "Invalid dtmin value"; + got = "dtmin=$x", + expected = "positive real number (> 0)", + suggestion = "Provide a positive minimum step size (e.g., 1e-6).", + context = "SciML dtmin validation", + ), + ), + ), + Strategies.OptionDefinition(; + name = :force_dtmin, + type = Bool, + default = Options.NotProvided, + description = "Whether to continue forcing minimum dt usage.", + ), + Strategies.OptionDefinition(; + name = :callback, + type = Any, + default = Options.NotProvided, + description = "Callback function for event handling.", + aliases = (:callbacks, :cb), + ), + Strategies.OptionDefinition(; + name = :progress, + type = Bool, + default = Options.NotProvided, + description = "Whether to show progress bar.", + aliases = (:verbose,), + ), + Strategies.OptionDefinition(; + name = :save_start, + type = Union{Bool, Symbol}, + default = :auto, + description = "Save initial condition in solution. Set `true`/`false` to force, or `:auto` to infer from call pattern (false for `flow(t0, x0[, p0], tf)`, true for `flow((t0, tf), x0[, p0])`).", + ), + Strategies.OptionDefinition(; + name = :save_end, + type = Bool, + default = Options.NotProvided, + description = "Whether to force saving the final timepoint.", + ), + Strategies.OptionDefinition(; + name = :internalnorm, + type = Function, + default = Common.real_norm, + description = "Internal norm for adaptive step-size control. " * + "Defaults to `real_norm`, which extracts the primal (Float64) " * + "part of ForwardDiff dual numbers to ensure grid invariance (IND) " * + "when ForwardDiff is loaded. Set to `DiffEqBase.ODE_DEFAULT_NORM` to use the SciML default.", + aliases = (:internal_norm, :norm), + ), + ) +end + +# ============================================================================= +# build_sciml_integrator โ€” actual implementation +# ============================================================================= + +# ============================================================================= +# Config-dependent option resolution +# ============================================================================= + +""" + _AUTO_OPTION_KEYS + +Tuple of option keys that support automatic resolution based on configuration type. + +These options use the `:auto` sentinel value in their metadata and are resolved +dynamically during integrator construction: +- For `StatePointConfig`: set to `false` (only final state needed) +- For `StateTrajectoryConfig`: set to `true` (full trajectory storage needed) + +Users can override automatic resolution by providing explicit `true`/`false` values +when constructing the integrator. +""" +const _AUTO_OPTION_KEYS = (:dense, :save_everystep, :save_start) + +""" +$(TYPEDSIGNATURES) + +Build a `SciML` integrator with validated options and pre-computed config-specific options. + +This function constructs a SciML integrator with automatic resolution of config-dependent +options. Options in `_AUTO_OPTION_KEYS` support the `:auto` sentinel value, which is +resolved based on the configuration type used during integration: +- For `StatePointConfig` (e.g., `flow(t0, x0, tf)`): options set to `false` to minimize memory + since only the final state is needed +- For `StateTrajectoryConfig` (e.g., `flow((t0, tf), x0)`): options set to `true` to enable + full trajectory storage and interpolation + +The resolved options are pre-computed and cached in the integrator for performance, +avoiding repeated resolution during integration. + +# Arguments +- `::Type{CTFlows.Integrators.SciMLTag}`: The SciML integrator tag type. +- `mode::Symbol`: Validation mode for strategy options (`:strict` or `:permissive`). +- `kwargs...`: User-provided option values. Explicit `true`/`false` values override + automatic `:auto` resolution. + +# Returns +- `CTFlows.Integrators.SciML`: Parametric SciML integrator with cached `options_point` + and `options_trajectory` fields. + +# Notes +- The `:auto` sentinel is defined in option metadata as `Union{Bool, Symbol}` with + default `:auto`. +- Pre-computation happens at construction time, not during integration. +- Config-specific options are returned by `Integrators.build_options` based on dispatch + on the configuration type. + +See also: [`CTFlows.Integrators.build_options`](@ref), [`CTFlows.Integrators.SciML`](@ref), +[`CTFlows.Configs.StatePointConfig`](@ref), [`CTFlows.Configs.StateTrajectoryConfig`](@ref). +""" +function CTFlows.Integrators.build_sciml_integrator( + ::Type{CTFlows.Integrators.SciMLTag}; mode::Symbol = :strict, kwargs..., +) + opts = Strategies.build_strategy_options(SciML; mode = mode, kwargs...) + raw = Strategies.options_dict(opts) + + # Check if algorithm is missing and raise PreconditionError + alg_val = raw[:alg] + if alg_val === missing + throw( + Exceptions.PreconditionError( + "No ODE algorithm specified and OrdinaryDiffEqTsit5 is not loaded"; + reason = "alg is missing", + suggestion = "Load OrdinaryDiffEqTsit5: using OrdinaryDiffEqTsit5\n" * + "Or specify an algorithm explicitly: SciML(alg=Vern6())\n" * + "Note: when specifying an algorithm, also load its package (e.g., using OrdinaryDiffEqVerner for Vern6)", + context = "SciML integrator construction", + ), + ) + end + + # Pre-compute options for StatePointConfig + options_point = copy(raw) + for key in _AUTO_OPTION_KEYS + get(options_point, key, :auto) === :auto && (options_point[key] = false) + end + + # Pre-compute options for StateTrajectoryConfig + options_trajectory = copy(raw) + for key in _AUTO_OPTION_KEYS + get(options_trajectory, key, :auto) === :auto && (options_trajectory[key] = true) + end + + return CTFlows.Integrators.SciML{typeof(opts), typeof(options_point), typeof(options_trajectory)}( + opts, options_point, options_trajectory + ) +end + +# ============================================================================= +# build_options โ€” config-dependent option resolution +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return pre-computed solver options for point integration configs. + +For point configs (e.g., StatePointConfig, HamiltonianPointConfig), options like +`dense`, `save_everystep`, and `save_start` are set to `false` to minimize memory +since only the final state is needed. + +# Arguments +- `integ::SciML`: The SciML integrator with pre-computed option caches. +- `config::Configs.AbstractPointConfig`: The point configuration. + +# Returns +- `Dict{Symbol,Any}`: Pre-computed options optimized for point integration. + +See also: [`Integrators.build_options`](@ref), [`Integrators.SciML`](@ref), [`CTFlows.Configs.AbstractPointConfig`](@ref). +""" +function Integrators.build_options(integ::SciML, config::Configs.AbstractPointConfig) + return integ.options_point +end + +""" +$(TYPEDSIGNATURES) + +Return pre-computed solver options for trajectory integration configs. + +For trajectory configs (e.g., StateTrajectoryConfig, HamiltonianTrajectoryConfig), +options like `dense`, `save_everystep`, and `save_start` are set to `true` to enable +full trajectory storage and interpolation. + +# Arguments +- `integ::SciML`: The SciML integrator with pre-computed option caches. +- `config::Configs.AbstractTrajectoryConfig`: The trajectory configuration. + +# Returns +- `Dict{Symbol,Any}`: Pre-computed options optimized for trajectory integration. + +See also: [`Integrators.build_options`](@ref), [`Integrators.SciML`](@ref), [`CTFlows.Configs.AbstractTrajectoryConfig`](@ref). +""" +function Integrators.build_options(integ::SciML, config::Configs.AbstractTrajectoryConfig) + return integ.options_trajectory +end + +""" +$(TYPEDSIGNATURES) + +Return pre-computed solver options for fallback case (Nothing). + +Defaults to trajectory options when no configuration is provided. + +# Arguments +- `integ::SciML`: The SciML integrator with pre-computed option caches. +- `config::Nothing`: No configuration provided (fallback). + +# Returns +- `Dict{Symbol,Any}`: Pre-computed options for trajectory integration (fallback). + +See also: [`Integrators.build_options`](@ref), [`Integrators.SciML`](@ref). +""" +function Integrators.build_options(integ::SciML, config::Nothing) + return integ.options_trajectory # fallback vers Trajectory par dรฉfaut +end diff --git a/ext/CTFlowsStaticArrays.jl b/ext/CTFlowsStaticArrays.jl new file mode 100644 index 00000000..83335780 --- /dev/null +++ b/ext/CTFlowsStaticArrays.jl @@ -0,0 +1,87 @@ +""" +CTFlowsStaticArrays + +Extension module providing type-stable support for `StaticArrays.SVector` and `SMatrix` in Hamiltonian flows. + +This module extends the internal `_ham_split` function to handle `SVector` and `SMatrix` inputs efficiently when the state dimension `N` is known at compile time. This enables zero-allocation operations for Hamiltonian systems with immutable array types. + +# Extension Details + +The extension provides: +- `_ham_split(u::SVector, N::Int)`: Type-stable splitting of `SVector` into `(x, p)` components using `SVector` constructors. +- `_ham_split(u::SMatrix, N::Int)`: Type-stable splitting of `SMatrix` into `(X, P)` components using `SMatrix` constructors. + +# Usage + +Load the extension with `using CTFlowsStaticArrays` after loading `CTFlows`: + +```julia +using CTFlows +using CTFlowsStaticArrays # Loads this extension + +# Now HamiltonianFlow works efficiently with SVector +hvf = HamiltonianVectorField((x, p) -> (p, -x); is_autonomous=true, is_variable=false) +flow = Flow(hvf; reltol=1e-8) +xf, pf = flow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + +# And with SMatrix for matrix initial conditions +X0 = SA[1.0 2.0; 3.0 4.0] +P0 = SA[0.0 0.0; 1.0 1.0] +Xf, Pf = flow(0.0, X0, P0, ฯ€/2) +``` + +# Notes +- This extension is optional and only needed when using `StaticArrays.SVector` or `SMatrix` with Hamiltonian flows. +- For standard `Vector` or `Matrix` types, the base implementation in `CTFlows.Systems` handles splitting correctly. +""" +module CTFlowsStaticArrays + +using CTFlows +import CTFlows.Systems: _ham_split +using StaticArrays: SVector, SMatrix + +""" +Type-stable split of a `SVector` into state and costate components. + +# Arguments +- `u::SVector`: Combined state vector `[x; p]`. +- `N::Int`: Known state dimension (compile-time). + +# Returns +- `Tuple{SVector{N}, SVector{N}}`: Tuple of `(x, p)` as `SVector`s. + +# Notes +- This method provides type stability for SciML's out-of-place ODE solvers when using `StaticArrays`. +- Used automatically when the `CTFlowsStaticArrays` extension is loaded. +""" +function _ham_split(u::SVector{NN, T}, N::Int) where {NN, T} + x = SVector{N, T}(ntuple(i -> u[i], Val(N))) + pk = SVector{N, T}(ntuple(i -> u[N+i], Val(N))) + return (x, pk) +end + +""" +Type-stable split of a `SMatrix` into state and costate components. + +# Arguments +- `u::SMatrix{NN,M,T}`: Combined state matrix stacked vertically `[X; P]`. +- `N::Int`: Known state dimension. + +# Returns +- `Tuple{SMatrix{N,M,T}, SMatrix{N,M,T}}`: Tuple of `(X, P)` as `SMatrix`s. + +# Notes +- This method provides type stability for SciML's out-of-place ODE solvers when using `StaticArrays` for matrix initial conditions. +- Constructs new `SMatrix` objects since views don't work with immutable static arrays. +- Uses 1D column-major linear indexing to enumerate elements: for result element `k` (1-based), + row = `(k-1) % N + 1`, col = `(k-1) รท N + 1`. +- Used automatically when the `CTFlowsStaticArrays` extension is loaded. +""" +function _ham_split(u::SMatrix{NN, M, T}, N::Int) where {NN, M, T} + # Enumerate result elements in column-major order via single index k + X = SMatrix{N, M, T}(ntuple(k -> u[(k-1) % N + 1, (k-1) รท N + 1], Val(N*M))) + P = SMatrix{N, M, T}(ntuple(k -> u[N + (k-1) % N + 1, (k-1) รท N + 1], Val(N*M))) + return (X, P) +end + +end # module CTFlowsStaticArrays diff --git a/reports/closures/callable_structs_report.md b/reports/closures/callable_structs_report.md new file mode 100644 index 00000000..9f78c1ec --- /dev/null +++ b/reports/closures/callable_structs_report.md @@ -0,0 +1,231 @@ +# Remplacement des closures par des structs appelables + +## 1. Contexte et motivation + +Le code actuel construit des closures via des fonctions `_build_rhs_*` ou `build_rhs`/`build_oop_rhs`. Ces closures capturent des variables concrรจtes et sont stockรฉes dans des champs typรฉs `RHS<:Function` ou crรฉรฉes ร  la volรฉe. Le type `Function` est opaque : le compilateur ne peut pas inspecter sa structure, ce qui pรฉnalise la composabilitรฉ, les stack traces et l'extensibilitรฉ. + +Les structs appelables (*functors*) sont l'idiome Julia pour ce cas : ils donnent un **type nommรฉ, paramรฉtrรฉ, inspectable**. + +--- + +## 2. Inventaire des closures actuelles + +### 2.1 `VectorFieldSystem` โ€” closures prรฉ-calculรฉes ร  la construction + +| Champ | Builder | Signature | +|---|---|---| +| `rhs` | `_build_rhs_vf_oop` / `_build_rhs_vf_ip` | `(du, u, ฮป, t) -> nothing` | +| `rhs_oop` | `_build_oop_rhs_vf_oop` / `_build_oop_rhs_vf_ip` | `(u, ฮป, t) -> du` | +| `rhs_oop_finalize` | `_build_finalize_rhs_vf_ip` | `(u, ฮป, t) -> du` | + +Ces trois closures capturent toutes le mรชme `vf` โ€” **aucune information supplรฉmentaire**. Le struct appelable est donc strictement รฉquivalent, avec un type nommรฉ en prime. + +### 2.2 `HamiltonianVectorFieldSystem` et `HamiltonianSystem` โ€” closures lazy (`build_rhs`, `build_oop_rhs`) + +Construites ร  l'appel de `build_problem`, elles capturent `vf`/`h`/`backend` **plus** `N::Int`, `cx`, `cp` (infรฉrรฉs depuis `x0`, `p0`). Ce sont ces captures supplรฉmentaires qui justifient la construction lazy โ€” et qui font aussi l'intรฉrรชt principal des structs appelables ici. + +--- + +## 3. Design proposรฉ + +### 3.1 Hiรฉrarchie abstraite des RHS + +```julia +abstract type AbstractRHS end +abstract type AbstractOoPRHS end +``` + +Permet le dispatch et des tests gรฉnรฉriques (`@test rhs isa AbstractRHS`). + +### 3.2 `VectorFieldSystem` โ€” trois functors + +```julia +# In-place, out-of-place vector field +struct VFOoPRHS{F, TD, VD} <: AbstractRHS + vf::Data.VectorField{F, TD, VD, Traits.OutOfPlace} +end +(f::VFOoPRHS)(du, u, ฮป, t) = (du .= f.vf(t, u, Common.variable(ฮป)); nothing) + +struct VFIpRHS{F, TD, VD} <: AbstractRHS + vf::Data.VectorField{F, TD, VD, Traits.InPlace} +end +(f::VFIpRHS)(du, u, ฮป, t) = (f.vf(du, t, u, Common.variable(ฮป)); nothing) + +# Out-of-place wrappers +struct VFOoPOoPRHS{F, TD, VD} <: AbstractOoPRHS + vf::Data.VectorField{F, TD, VD, Traits.OutOfPlace} +end +(f::VFOoPOoPRHS)(u, ฮป, t) = f.vf(t, u, Common.variable(ฮป)) + +struct VFIpOoPRHS{F, TD, VD} <: AbstractOoPRHS + vf::Data.VectorField{F, TD, VD, Traits.InPlace} +end +(f::VFIpOoPRHS)(u, ฮป, t) = (dx = similar(u); f.vf(dx, t, u, Common.variable(ฮป)); dx) + +struct VFIpFinalizeRHS{F, TD, VD} <: AbstractOoPRHS + vf::Data.VectorField{F, TD, VD, Traits.InPlace} +end +(f::VFIpFinalizeRHS)(u, ฮป, t) = (dx = similar(u); f.vf(dx, t, u, Common.variable(ฮป)); typeof(u)(dx)) +``` + +`VectorFieldSystem` perd alors ses paramรจtres `RHS` et `OOPROHS` qui deviennent **dรฉterministes** : + +```julia +struct VectorFieldSystem{F, TD, VD, MD} <: AbstractStateSystem{TD, VD} + vf ::Data.VectorField{F, TD, VD, MD} + rhs ::VFOoPRHS{F, TD, VD} # ou VFIpRHS selon MD + rhs_oop # VFOoPOoPRHS / VFIpOoPRHS + rhs_oop_finalize # VFIpFinalizeRHS ou Nothing +end +``` + +Les paramรจtres `RHS<:Function` et `OOPROHS<:Function` disparaissent : le type du champ est **infรฉrรฉ statiquement** depuis `F, TD, VD, MD`. + +### 3.3 `HamiltonianVectorFieldSystem` โ€” functors lazy avec cache de forme + +Le gain principal est ici : les captures `N`, `cx`, `cp` sont des champs typรฉs. + +```julia +struct HVFIpRHS{F, TD, VD, CX, CP} <: AbstractRHS + hvf ::Data.HamiltonianVectorField{F, TD, VD, Traits.InPlace} + N ::Int + cx ::CX # typeof(_make_coerce(x0)) + cp ::CP +end +(f::HVFIpRHS)(du, u, ฮป, t) = begin + x, p = _ham_split(u, f.N) + dx, dp = _ham_split(du, f.N) + f.hvf(dx, dp, t, f.cx(x), f.cp(p), Common.variable(ฮป)) + nothing +end + +struct HVFOoPRHS{F, TD, VD, CX, CP} <: AbstractRHS + hvf ::Data.HamiltonianVectorField{F, TD, VD, Traits.OutOfPlace} + N ::Int + cx ::CX + cp ::CP +end +(f::HVFOoPRHS)(du, u, ฮป, t) = begin + x, p = _ham_split(u, f.N) + dx, dp = f.hvf(t, f.cx(x), f.cp(p), Common.variable(ฮป)) + _ham_assign!(du, dx, dp, f.N) + nothing +end +``` + +`build_rhs` devient un constructeur de functor plutรดt qu'une factory de closure : + +```julia +function build_rhs(sys::HamiltonianVectorFieldSystem{F,TD,VD,Traits.InPlace}, x0, p0) where {F,TD,VD} + return HVFIpRHS(sys.hvf, _state_dim(x0), _make_coerce(x0), _make_coerce(p0)) +end +``` + +### 3.4 `HamiltonianSystem` (AD) โ€” idem + +```julia +struct HamIpRHS{F, TD, VD, B, CX, CP} <: AbstractRHS + h ::Data.Hamiltonian{F, TD, VD} + backend ::B + N ::Int + cx ::CX + cp ::CP +end +(f::HamIpRHS)(du, u, ฮป, t) = begin + x, p = _ham_split(u, f.N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient( + f.backend, f.h, t, f.cx(x), f.cp(p), + Common.variable(ฮป), Common.cache(ฮป)) + _ham_assign!(du, โˆ‚p, -โˆ‚x, f.N) + nothing +end +``` + +--- + +## 4. Opportunitรฉs de cache + +### 4.1 Buffer prรฉ-allouรฉ dans `VFIpOoPRHS` + +Actuellement, `_build_oop_rhs_vf_ip` fait `similar(u)` **ร  chaque appel**. Avec un functor, on peut stocker le buffer : + +```julia +struct VFIpOoPRHS{F, TD, VD, BUF} <: AbstractOoPRHS + vf ::Data.VectorField{F, TD, VD, Traits.InPlace} + buf ::BUF # Vector prรฉ-allouรฉ +end +(f::VFIpOoPRHS)(u, ฮป, t) = (f.vf(f.buf, t, u, Common.variable(ฮป)); copy(f.buf)) +``` + +Constructeur : +```julia +VFIpOoPRHS(vf, u0::AbstractVector) = VFIpOoPRHS(vf, similar(u0)) +``` + +> โš ๏ธ Ce buffer n'est **pas thread-safe**. Pour du multi-threading, utiliser un `Vector` de buffers ou un `TaskLocalValue` (package `ThreadingUtilities`). + +### 4.2 Buffer dans `HVFIpOoPRHS` + +Mรชme logique pour les Hamiltonians in-place out-of-place : `dx` et `dp` peuvent รชtre prรฉ-allouรฉs. + +```julia +struct HVFIpOoPRHS{F, TD, VD, CX, CP, BUF} <: AbstractOoPRHS + hvf ::Data.HamiltonianVectorField{F, TD, VD, Traits.InPlace} + N ::Int + cx ::CX + cp ::CP + buf ::BUF # similar(u0) pour [dx; dp] +end +``` + +### 4.3 Cache AD dans `HamIpRHS` + +Le backend AD peut nรฉcessiter des buffers internes (seed vectors, dual number buffers). Ces derniers sont actuellement gรฉrรฉs par `Common.cache(ฮป)` passรฉ ร  `hamiltonian_gradient`. Si ce cache est allouรฉ ร  chaque `build_problem`, le stocker dans le functor est une alternative : + +```julia +struct HamIpRHS{..., CACHE} + ... + ad_cache ::CACHE +end +``` + +Cela suppose que `hamiltonian_gradient` accepte un cache externe โ€” ร  valider selon l'API de `Differentiation`. + +--- + +## 5. Impact sur `VectorFieldSystem` + +Avec les functors, les paramรจtres de type `RHS` et `OOPROHS` (actuellement `<:Function`) peuvent รชtre **supprimรฉs** ou remplacรฉs par des types concrets dรฉterministes : + +```julia +# Avant +struct VectorFieldSystem{F, TD, VD, MD, RHS<:Function, OOPROHS<:Function, FINRHS} ... + +# Aprรจs (OutOfPlace) +struct VectorFieldSystem{F, TD, VD, Traits.OutOfPlace} ... +# les champs rhs/rhs_oop ont des types entiรจrement dรฉterminรฉs par F,TD,VD +``` + +Avantage : `typeof(sys)` encode **toute** l'information structurelle. Pas de `typeof(rhs)` dans le constructeur. + +--- + +## 6. Rรฉcapitulatif des bรฉnรฉfices + +| Aspect | Closure actuelle | Struct appelable | +|---|---|---| +| Type-stabilitรฉ | โœ… (captures concrรจtes) | โœ… | +| Stack traces | `var"#3#4"` | `HVFIpRHS{...}` | +| Dispatch sur le type du RHS | โŒ | โœ… | +| Buffer prรฉ-allouรฉ | โŒ | โœ… | +| Sรฉrialisation / precompilation | Fragile | Robuste | +| Paramรจtres `RHS<:Function` dans le systรจme | Nรฉcessaires | Supprimables | +| Extensibilitรฉ (extension de package) | โŒ | โœ… (mรฉthode sur le functor) | + +--- + +## 7. Recommandation de prioritรฉ + +1. **Court terme** โ€” `VectorFieldSystem` : gain immรฉdiat de lisibilitรฉ et suppression des paramรจtres de type `RHS`/`OOPROHS`. Changement mรฉcanique, risque faible. +2. **Moyen terme** โ€” Hamiltonians lazy (`build_rhs`) : le gain de buffer prรฉ-allouรฉ est significatif si les flows sont appelรฉs en boucle (optimisation, tir multiple). +3. **Long terme** โ€” cache AD dans `HamIpRHS` : dรฉpend de l'API de `Differentiation`, ร  รฉvaluer au cas par cas. diff --git a/reports/closures/closures_inventory.md b/reports/closures/closures_inventory.md new file mode 100644 index 00000000..58f9d32c --- /dev/null +++ b/reports/closures/closures_inventory.md @@ -0,0 +1,143 @@ +# Inventaire complet des closures dans CTFlows.jl + +## Rรฉsumรฉ + +- **Total closures actives:** 17 +- **Closures prรฉ-calculรฉes:** 10 (5 VectorField + 5 SciMLFunctionSystem) +- **Closures lazy:** 7 (4 HamiltonianVectorField + 3 HamiltonianSystem) +- **Fonctions utilisateur:** jumps dans MultiPhase (non comptรฉes comme closures du package) + +--- + +## 1. VectorFieldSystem โ€” closures prรฉ-calculรฉes (construction) + +**Fichier:** `src/Systems/vector_field_system.jl` + +| Builder | Ligne | Signature | Capture | Notes | +| --- | --- | --- | --- | --- | +| `_build_rhs_vf_oop` | 107-109 | `(du, u, ฮป, t) -> nothing` | `vf` | OutOfPlace vector field | +| `_build_rhs_vf_ip` | 126-128 | `(du, u, ฮป, t) -> nothing` | `vf` | InPlace vector field | +| `_build_oop_rhs_vf_oop` | 144-146 | `(u, ฮป, t) -> du` | `vf` | OutOfPlace vector field | +| `_build_oop_rhs_vf_ip` | 163-169 | `(u, ฮป, t) -> du` | `vf` | InPlace vector field, alloue `similar(u)` ร  chaque appel | +| `_build_finalize_rhs_vf_ip` | 187-193 | `(u, ฮป, t) -> du` | `vf` | InPlace vector field, convertit avec `typeof(u)(dx)` | + +**Stockage:** Champs `rhs`, `rhs_oop`, `rhs_oop_finalize` dans le struct. + +--- + +## 2. HamiltonianVectorFieldSystem โ€” closures lazy (build_problem) + +**Fichier:** `src/Systems/hamiltonian_vector_field_system.jl` + +| Builder | Ligne | Signature | Capture | Notes | +| --- | --- | --- | --- | --- | +| `build_rhs` (OutOfPlace) | 150-161 | `(du, u, ฮป, t) -> nothing` | `hvf`, `N`, `cx`, `cp` | Lazy, construit ร  l'appel de `build_problem` | +| `build_rhs` (InPlace) | 163-174 | `(du, u, ฮป, t) -> nothing` | `hvf`, `N`, `cx`, `cp` | Lazy, construit ร  l'appel de `build_problem` | +| `build_oop_rhs` (OutOfPlace) | 192-202 | `(u, ฮป, t) -> du` | `hvf`, `N`, `cx`, `cp` | Lazy, construit ร  l'appel de `build_problem` | +| `build_oop_rhs` (InPlace) | 204-223 | `(u, ฮป, t) -> du` | `hvf`, `N`, `cx`, `cp`, `is_u0_mutable` | Lazy, alloue `dx`, `dp` ร  chaque appel | + +**Construction lazy:** Appelรฉes via `build_problem` avec `x0`, `p0` concrets. + +--- + +## 3. HamiltonianSystem โ€” closures lazy avec AD + +**Fichier:** `src/Systems/hamiltonian_system.jl` + +| Builder | Ligne | Signature | Capture | Notes | +| --- | --- | --- | --- | --- | +| `build_rhs` | 106-117 | `(du, u, ฮป, t) -> nothing` | `h`, `backend`, `N`, `cx`, `cp` | Lazy, utilise `hamiltonian_gradient` | +| `build_oop_rhs` | 135-145 | `(u, ฮป, t) -> du` | `h`, `backend`, `N`, `cx`, `cp` | Lazy, utilise `hamiltonian_gradient` | +| `build_rhs_augmented` | 168-179 | `(du, u, ฮป, t) -> nothing` | `h`, `backend`, `n_x`, `n_v` | Lazy, pour systรจmes augmentรฉs avec coรปtate variable | + +**Construction lazy:** Appelรฉes via `build_problem` ou `build_rhs_augmented(sys, n_x, n_v)`. + +--- + +## 4. SciMLFunctionSystem โ€” closures prรฉ-calculรฉes (extension) + +**Fichier:** `ext/CTFlowsSciML/sciml_function_system.jl` + +| Builder | Ligne | Signature | Capture | Notes | +| --- | --- | --- | --- | --- | +| `rhs_fn` (in-place) | 62 | `(du, u, ฮป, t) -> nothing` | `f` (SciML ODEFunction) | Pour `AbstractODEFunction{true}` | +| `rhs_oop_fn` (in-place wrapper) | 65-69 | `(u, ฮป, t) -> du` | `f` (SciML ODEFunction) | Pour `AbstractODEFunction{true}`, alloue `similar(u)` | +| `rhs_oop_finalize_fn` (in-place finalize) | 72-76 | `(u, ฮป, t) -> du` | `f` (SciML ODEFunction) | Pour `AbstractODEFunction{true}`, convertit avec `typeof(u)(dx)` | +| `rhs_fn` (out-of-place) | 85 | `(du, u, ฮป, t) -> nothing` | `f` (SciML ODEFunction) | Pour `AbstractODEFunction{false}` | +| `rhs_oop_fn` (out-of-place direct) | 88 | `(u, ฮป, t) -> du` | `f` (SciML ODEFunction) | Pour `AbstractODEFunction{false}` | + +**Stockage:** Champs `rhs_fn`, `rhs_oop_fn`, `rhs_oop_finalize_fn` dans le struct. + +--- + +## 5. MultiPhase โ€” jump functions (non-RHS) + +**Fichiers:** `src/MultiPhase/concatenation.jl` et `src/MultiPhase/multiphase_flow.jl` + +Les jumps sont des **fonctions utilisateur** stockรฉes dans `jumps::Vector{<:Any}`, pas des closures construites par le package. Elles sont appliquรฉes via `_apply_jump` (ligne 280-341 dans `calling.jl`). + +**Exemples d'utilisation:** + +- `jump = x -> 2 * x` (state flow) +- `jump_x = x -> 2 * x; jump_p = p -> 3 * p` (Hamiltonian flow) + +Ces fonctions sont fournies par l'utilisateur et ne sont pas comptรฉes comme closures du package. + +--- + +## 6. AbstractSystem โ€” stubs de documentation + +**Fichier:** `src/Systems/abstract_system.jl` + +Les lignes 28 et 257 contiennent des exemples de closures dans les docstrings (`(du, u, p, t) -> du .= sys.data .* u`), mais ce ne sont pas du code actif. + +--- + +## Analyse par catรฉgorie + +### Closures prรฉ-calculรฉes (10) + +- **VectorFieldSystem:** 5 closures + - 2 in-place (oop/ip) + - 2 out-of-place (oop/ip) + - 1 finalize (ip) + +- **SciMLFunctionSystem:** 5 closures + - 3 pour in-place SciML functions + - 2 pour out-of-place SciML functions + +### Closures lazy (7) + +- **HamiltonianVectorFieldSystem:** 4 closures + - 2 in-place (oop/ip) + - 2 out-of-place (oop/ip) + +- **HamiltonianSystem:** 3 closures + - 1 in-place standard + - 1 out-of-place standard + - 1 in-place augmentรฉ + +### Opportunitรฉs d'optimisation + +**Buffer prรฉ-allouable:** + +- `_build_oop_rhs_vf_ip` (VectorFieldSystem) โ€” alloue `similar(u)` ร  chaque appel +- `build_oop_rhs` (HamiltonianVectorFieldSystem InPlace) โ€” alloue `dx`, `dp` ร  chaque appel +- `rhs_oop_fn` (SciMLFunctionSystem in-place) โ€” alloue `similar(u)` ร  chaque appel + +**Cache AD:** + +- `build_rhs` et `build_oop_rhs` (HamiltonianSystem) โ€” utilisent `Common.cache(ฮป)` passรฉ ร  `hamiltonian_gradient` + +--- + +## Comparaison avec le rapport callable_structs_report.md + +Le rapport `callable_structs_report.md` documente: + +- โœ… Les 5 closures de VectorFieldSystem +- โœ… Les 4 closures de HamiltonianVectorFieldSystem +- โœ… Les 3 closures de HamiltonianSystem +- โŒ **Manque:** Les 5 closures de SciMLFunctionSystem (extension CTFlowsSciML) + +Ce rapport complรจte l'inventaire en ajoutant les closures de l'extension SciML. diff --git a/reports/docs/data.md b/reports/docs/data.md new file mode 100644 index 00000000..85ea9ec7 --- /dev/null +++ b/reports/docs/data.md @@ -0,0 +1,63 @@ +# Data + +## Overview + +The Data module provides data structures for vector fields and Hamiltonian vector fields with trait-based encoding of properties. These structures encapsulate vector-field functions together with their time-dependence and variable-dependence traits, enabling compile-time dispatch throughout the CTFlows stack. + +## Key Types + +### Abstract Types + +- `AbstractVectorField{TD, VD}` - Abstract base type for all vector fields with type parameters for time dependence (TD) and variable dependence (VD) + +### Concrete Types + +- `VectorField{F, TD, VD}` - Standard vector field wrapping a function `f(u, p, t)` or `f(u, p)` depending on time dependence +- `HamiltonianVectorField{F, TD, VD}` - Hamiltonian vector field wrapping a function that returns state and costate derivatives + +## Traits + +Vector fields use two trait type parameters: + +- **Time Dependence (TD)**: `Autonomous` or `NonAutonomous` + - Encodes whether the vector field depends explicitly on time + - Affects function signature (time parameter presence) + +- **Variable Dependence (VD)**: `Fixed` or `NonFixed` + - Encodes whether parameters are fixed or variable + - Used for optimization and sensitivity analysis contexts + +Trait accessors are implemented at the abstract level: +- `time_dependence(vf::AbstractVectorField)` - Returns the time dependence trait type +- `variable_dependence(vf::AbstractVectorField)` - Returns the variable dependence trait type +- `has_time_dependence_trait(vf::AbstractVectorField)` - Always returns true +- `has_variable_dependence_trait(vf::AbstractVectorField)` - Always returns true + +## Usage Pattern + +The typical usage pattern for data structures: + +1. Define a vector field function with appropriate signature +2. Create a `VectorField` or `HamiltonianVectorField` wrapping the function +3. Specify time and variable dependence via keyword arguments or constructor parameters +4. The traits are encoded in the type parameters for compile-time dispatch +5. Vector fields are then used to build systems (see [systems.md](systems.md)) + +Vector fields serve as the foundational data layer - they encode the dynamics with trait information that propagates through systems, flows, and solutions. + +## Source Files + +- `src/Data/abstract_vector_field.jl` - Abstract type definition and trait accessors +- `src/Data/vector_field.jl` - Concrete VectorField implementation +- `src/Data/hamiltonian_vector_field.jl` - Concrete HamiltonianVectorField implementation + +## Test Files + +- `test/suite/data/test_abstract_vector_field.jl` - Abstract vector field tests +- `test/suite/data/test_vector_field.jl` - VectorField tests +- `test/suite/data/test_hamiltonian_vector_field.jl` - HamiltonianVectorField tests + +## See Also + +- [traits.md](traits.md) - Trait system overview +- [systems.md](systems.md) - How vector fields are used to build systems diff --git a/reports/docs/flows.md b/reports/docs/flows.md new file mode 100644 index 00000000..8e99940a --- /dev/null +++ b/reports/docs/flows.md @@ -0,0 +1,83 @@ +# Flows + +## Overview + +The Flows module provides flow types that combine systems with integrators. A flow is a callable object that exposes the integration protocol - it carries no business logic of its own, but serves as the interface for performing ODE integration. Flows inherit traits from their systems, enabling compile-time dispatch throughout the integration process. + +## Key Types + +### Abstract Types + +- `AbstractFlow{TD, VD}` - Abstract base type for all flows with time dependence (TD) and variable dependence (VD) traits +- `AbstractStateFlow{TD, VD, S}` - Abstract type for state flows (non-Hamiltonian), parameterized by system type S +- `AbstractHamiltonianFlow{TD, VD, S}` - Abstract type for Hamiltonian flows (state + costate), parameterized by system type S + +### Concrete Types + +- `Flow{S, I}` - Concrete flow combining a system S with an integrator I +- `StateFlow{S, I}` - Concrete state flow (alias for Flow with state system) +- `HamiltonianFlow{S, I}` - Concrete Hamiltonian flow (alias for Flow with Hamiltonian system) + +## Traits + +Flows inherit their traits from the underlying system: + +- **Time Dependence (TD)**: `Autonomous` or `NonAutonomous` - inherited from system +- **Variable Dependence (VD)**: `Fixed` or `NonFixed` - inherited from system + +Trait accessors are implemented at the abstract level: +- `time_dependence(flow::AbstractFlow)` - Returns the time dependence trait type +- `variable_dependence(flow::AbstractFlow)` - Returns the variable dependence trait type +- `has_time_dependence_trait(flow::AbstractFlow)` - Always returns true +- `has_variable_dependence_trait(flow::AbstractFlow)` - Always returns true + +## Usage Pattern + +The typical usage pattern for flows: + +1. Create a system (see [systems.md](systems.md)) +2. Create an integrator (see [integrators.md](integrators.md)) +3. Build a flow from the system and integrator using `build_flow` +4. Call the flow to perform integration: `flow(t0, x0, tf)` for state or `flow(t0, x0, p0, tf)` for Hamiltonian +5. The flow returns a solution (see [solutions.md](solutions.md)) + +### Contract Methods + +All concrete flows must implement: + +- `system(flow::AbstractFlow)` - Returns the associated AbstractSystem +- `integrator(flow::AbstractFlow)` - Returns the associated AbstractIntegrator + +### Callable Interface + +Flows are callable objects with the following signatures: + +- For state flows: `(flow)(t0, x0, tf)` - Integrates from initial state x0 at time t0 to final time tf +- For Hamiltonian flows: `(flow)(t0, x0, p0, tf)` - Integrates from initial state x0 and costate p0 at time t0 to final time tf + +### Building Functions + +- `build_flow(system, integrator)` - Constructs a flow from a system and integrator + +Flows serve as the integration interface layer - they combine the dynamics (system) with the numerical method (integrator) to provide a callable integration operation. + +## Source Files + +- `src/Flows/abstract_flow.jl` - Abstract type definitions and contract methods +- `src/Flows/flow.jl` - Concrete Flow implementation +- `src/Flows/building.jl` - Flow building functions +- `src/Flows/calling.jl` - Flow callable interface implementation + +## Test Files + +- `test/suite/flows/test_abstract_flow.jl` - Abstract flow tests +- `test/suite/flows/test_flow.jl` - Flow tests +- `test/suite/flows/test_building.jl` - Flow building tests +- `test/suite/flows/test_calling.jl` - Flow callable interface tests + +## See Also + +- [systems.md](systems.md) - System types used in flows +- [integrators.md](integrators.md) - Integrator types used in flows +- [solutions.md](solutions.md) - Solution types returned by flows +- [multiphase.md](multiphase.md) - Multi-phase flow concatenation diff --git a/reports/docs/index.md b/reports/docs/index.md new file mode 100644 index 00000000..be32fbd9 --- /dev/null +++ b/reports/docs/index.md @@ -0,0 +1,102 @@ +# CTFlows Internal Documentation + +This directory contains internal documentation notes for CTFlows, providing a conceptual overview of the package's capabilities. These notes serve as a foundation for future official documentation. + +## What is CTFlows? + +CTFlows is a Julia package for flow-based integration and optimal control. It provides a modular architecture for building and integrating dynamical systems using flow-based approaches. The package is organized into specialized submodules, with all public symbols accessed via qualified paths (e.g., `CTFlows.Systems.AbstractSystem`). + +## Module Architecture + +CTFlows is organized into the following submodules (in dependency order): + +``` +Common + โ”œโ”€โ”€ Data + โ”œโ”€โ”€ Systems + โ”œโ”€โ”€ Integrators + โ”œโ”€โ”€ Solutions + โ”œโ”€โ”€ Flows + โ””โ”€โ”€ MultiPhase +``` + +### Common +Shared utilities and types, including the trait system (mode traits, content traits, time dependence, variable dependence) and configuration types. + +### Data +Data structures for vector fields and Hamiltonian vector fields with trait-based encoding of properties. + +### Systems +System types and contracts for dynamical systems, including state systems and Hamiltonian systems. + +### Integrators +ODE integrator strategies, integrating with the CTSolvers strategy pattern and SciML ecosystem. + +### Solutions +Solution types and solution building functions for integration results. + +### Flows +Flow types that combine systems with integrators, providing callable interfaces for integration. + +### MultiPhase +Multi-phase flow concatenation and sequential integration with switching times and jumps. + +## Concept Documentation + +The following documents provide detailed conceptual overviews of each major theme: + +- [traits.md](traits.md) - Trait system for compile-time dispatch +- [data.md](data.md) - Vector field data structures +- [systems.md](systems.md) - System types and contracts +- [flows.md](flows.md) - Flow types (system + integrator) +- [integrators.md](integrators.md) - ODE integrator strategies +- [solutions.md](solutions.md) - Solution types and accessors +- [multiphase.md](multiphase.md) - Multi-phase concatenation + +## Key Concepts + +### Traits +CTFlows uses a trait system for compile-time dispatch. Traits are empty marker types used as type parameters to encode configuration properties: +- **Mode traits**: PointTrait (point-to-point integration) vs TrajectoryTrait (full trajectory) +- **Content traits**: StateTrait (state only) vs HamiltonianTrait (state + costate) +- **Time dependence**: Autonomous vs NonAutonomous +- **Variable dependence**: Fixed vs NonFixed + +### Data Flow +The typical workflow in CTFlows follows this pattern: + +1. Define a vector field (Data module) with appropriate traits +2. Build a system from the vector field (Systems module) +3. Create an integrator (Integrators module) +4. Build a flow combining system and integrator (Flows module) +5. Call the flow to integrate and obtain a solution (Solutions module) +6. Optionally concatenate multiple flows for multi-phase problems (MultiPhase module) + +### Trait Inheritance +Traits propagate through the type hierarchy: +- Vector fields have time and variable dependence traits +- Systems inherit traits from their underlying vector fields +- Flows inherit traits from their systems +- This enables compile-time dispatch throughout the stack + +## Source Files + +- Main module: `src/CTFlows.jl` +- Common: `src/Common/` +- Data: `src/Data/` +- Systems: `src/Systems/` +- Integrators: `src/Integrators/` +- Solutions: `src/Solutions/` +- Flows: `src/Flows/` +- MultiPhase: `src/MultiPhase/` + +## Test Files + +Test examples demonstrating usage can be found in: +- `test/suite/common/` +- `test/suite/data/` +- `test/suite/systems/` +- `test/suite/integrators/` +- `test/suite/solutions/` +- `test/suite/flows/` +- `test/suite/multiphase/` diff --git a/reports/docs/integrators.md b/reports/docs/integrators.md new file mode 100644 index 00000000..c2c7c77d --- /dev/null +++ b/reports/docs/integrators.md @@ -0,0 +1,73 @@ +# Integrators + +## Overview + +The Integrators module provides ODE integrator strategies for CTFlows. Integrators are responsible for the numerical solution of ODE systems, wrapping external ODE solver libraries (primarily the SciML ecosystem) and providing a strategy pattern interface consistent with the CTSolvers framework. + +## Key Types + +### Abstract Types + +- `AbstractIntegrator` - Abstract base type for all integrators, inherits from CTSolvers.Strategies.AbstractStrategy +- `AbstractIntegrationResult` - Abstract type for raw ODE integration results + +### Concrete Types + +- `SciML{Tag}` - Integrator using SciML ecosystem solvers, parameterized by tag type +- `SciMLTag` - Tag marking SciML-based integrator implementations +- `Tsit5Tag` - Tag for Tsit5 (Tsitouras 5) SciML solver + +### Result Types + +- `SciMLIntegrationResult{S}` - Wrapper for SciML ODE solution objects + +## Traits + +Integrators do not have trait type parameters like data, systems, or flows. Instead, they are strategy objects that can be configured via their tag types and options. The trait information (time dependence, variable dependence) is carried by the systems and flows that use the integrators. + +## Usage Pattern + +The typical usage pattern for integrators: + +1. Create an integrator using `build_sciml_integrator` or `build_integrator` +2. The integrator is configured with a tag (e.g., Tsit5Tag) and options +3. The integrator is combined with a system to create a flow (see [flows.md](flows.md)) +4. When the flow is called, the integrator performs the numerical integration +5. The integrator returns an integration result that is wrapped in a solution type + +### Building Functions + +- `build_sciml_integrator(tag; options...)` - Builds a SciML integrator with specified tag and options +- `build_integrator(tag; options...)` - Generic integrator builder (dispatches based on tag) + +### Integration Functions + +- `build_problem(system, config)` - Builds an ODE problem from a system and configuration +- `solve_problem(problem, integrator)` - Solves an ODE problem with an integrator +- `merge(result1, result2)` - Merges integration results (used in multi-phase contexts) + +### Result Accessors + +- `final_state(result)` - Extracts the final state from an integration result +- `times(result)` - Extracts the time points from an integration result +- `evaluate_at(result, t)` - Evaluates the solution at a specific time + +Integrators serve as the numerical method layer - they provide the actual ODE solving capability that is called by flows. + +## Source Files + +- `src/Integrators/abstract_integrator.jl` - Abstract integrator type definition +- `src/Integrators/integration_result.jl` - Integration result types and accessors +- `src/Integrators/sciml.jl` - SciML integrator implementation +- `src/Integrators/building.jl` - Integrator and problem building functions + +## Test Files + +- `test/suite/integrators/test_abstract_integrator.jl` - Abstract integrator tests +- `test/suite/integrators/test_sciml.jl` - SciML integrator tests +- `test/suite/integrators/test_building.jl` - Integrator building tests + +## See Also + +- [flows.md](flows.md) - How integrators are combined with systems to create flows +- [solutions.md](solutions.md) - Solution types that wrap integration results diff --git a/reports/docs/multiphase.md b/reports/docs/multiphase.md new file mode 100644 index 00000000..f0510683 --- /dev/null +++ b/reports/docs/multiphase.md @@ -0,0 +1,120 @@ +# MultiPhase + +## Overview + +The MultiPhase module provides multi-phase flow concatenation and sequential integration. It enables combining multiple flows with switching times and optional jumps to solve problems where the dynamics change at discrete time points (e.g., control switches, phase transitions). The module implements exact sequential integration, ensuring continuity or specified jumps at phase boundaries. + +## Key Types + +### Concrete Types + +- `MultiPhaseStateFlow{TD, VD, N}` - Multi-phase flow for state systems, parameterized by time dependence (TD), variable dependence (VD), and number of phases (N) +- `MultiPhaseHamiltonianFlow{TD, VD, N}` - Multi-phase flow for Hamiltonian systems (state + costate), parameterized by time dependence (TD), variable dependence (VD), and number of phases (N) + +## Traits + +Multi-phase flows inherit traits from their constituent flows: + +- **Time Dependence (TD)**: `Autonomous` or `NonAutonomous` - inherited from the flows +- **Variable Dependence (VD)**: `Fixed` or `NonFixed` - inherited from the flows + +All flows in a multi-phase sequence must have compatible traits (same time and variable dependence). + +## Usage Pattern + +The typical usage pattern for multi-phase flows: + +1. Create individual flows for each phase (see [flows.md](flows.md)) +2. Specify switching times between phases +3. Optionally specify jump functions at phase boundaries +4. Concatenate flows using the `*` operator +5. Call the multi-phase flow to perform sequential integration +6. Access individual phases, switching times, and jumps using accessor functions + +### Concatenation Operators + +- `flow1 * flow2` - Concatenates two flows (infix operator) + +Note: only the `*` operator is implemented for concatenation in CTFlows. The `โˆ˜` operator is not defined. + +### Accessors + +- `n_phases(multi_flow)` - Returns the number of phases +- `get_flow(multi_flow, i)` - Returns the i-th flow in the sequence +- `get_switching_time(multi_flow, i)` - Returns the switching time before phase i +- `get_jump(multi_flow, i)` - Returns the jump function at phase boundary i (if any) +- `get_flows(multi_flow)` - Returns all flows as a tuple +- `get_switching_times(multi_flow)` - Returns all switching times as a tuple +- `get_jumps(multi_flow)` - Returns all jump functions as a tuple + +## Algebraic Structure and Associativity + +### Left Action (partial) of Phase Insertions on Flows + +Let F be the set of flows (including already concatenated multi-phase flows). Let E be the set of phase insertions of the form e = (t, g), (t, j, g) for state flows, or (t, jx, jp, g) for Hamiltonian flows, where t โˆˆ โ„ is a switching time, g is a flow, and j, jx, jp are jump functions. + +Define a partial left action + +- f โ‹† e := f * e, with domain restricted by strictly increasing switching times. + +If f already contains switching times (tโ‚ < โ‹ฏ < t_k), then f โ‹† (t, โ€ฆ) is defined only when t > t_k. This is enforced in code by `_check_switching_times_order`; violations raise a `PreconditionError`. + +Under this precondition, successive insertions are associative on the left: for eโ‚ = (tโ‚, g) and eโ‚‚ = (tโ‚‚, h) with tโ‚ < tโ‚‚ and both defined for f, + +- (f โ‹† eโ‚) โ‹† eโ‚‚ = f โ‹† eโ‚ โ‹† eโ‚‚, + +and both sides yield the same multi-phase flow with phases of f, then g at tโ‚, then h at tโ‚‚. Internally, concatenation constructs new flows by concatenating (vcat) the lists of flows, switching-times, and jumps; this operation is associative. + +Note that an expression like f โ‹† (eโ‚ โ‹† eโ‚‚) is not meaningful here: E elements are not flows, and there is no binary operation defined on E in this design. Composition is achieved by repeated application of the left action. + +### Not a Group Action + +This structure is not a group action: E has no inverses and the action is partial due to the time-order precondition. One can view CTFlows as implementing a partial left action of the free monoid of well-formed insertion sequences (monotone times) on flows. + +### Why not a Right Action or Non-Associative Variant? + +One could imagine a right action semantics where adding (tโ‚‚, h) after F = f * (tโ‚, g) would allow tโ‚‚ < tโ‚ and defer routing to evaluation time (e.g., running f up to tโ‚‚, then h, etc.). CTFlows deliberately does not implement this: + +- Strictly increasing switching times establish a canonical, evaluation-independent partition of the time axis. +- This simplifies evaluation/merging and error handling, and avoids time-queryโ€“dependent routing. +- The left-insertion operation remains associative under the monotonicity precondition. + +Hence, in CTFlows it is valid to build G = (f * (tโ‚, g)) * (tโ‚‚, h) only when tโ‚‚ > tโ‚. Allowing tโ‚‚ < tโ‚ would require different semantics (non-associative right action or time-dependent dispatch) that CTFlows rejects by design. + +### Calling Interface + +Multi-phase flows are callable with the same signatures as single-phase flows: + +- For state flows: `(multi_flow)(t0, x0, tf)` - Sequentially integrates through all phases +- For Hamiltonian flows: `(multi_flow)(t0, x0, p0, tf)` - Sequentially integrates through all phases + +### Sequential Integration + +When a multi-phase flow is called: + +1. Integration starts at t0 with initial condition +2. Each phase is integrated up to its switching time +3. At each switching time, the jump function (if provided) is applied to the state +4. The result becomes the initial condition for the next phase +5. Process continues until final time tf +6. Results from all phases are merged into a single solution + +Multi-phase flows serve as the composition layer - they enable complex multi-stage problems to be expressed as sequences of simpler flows with exact handling of phase transitions. + +## Source Files + +- `src/MultiPhase/multiphase_flow.jl` - Multi-phase flow type definitions +- `src/MultiPhase/concatenation.jl` - Concatenation operators and building functions +- `src/MultiPhase/calling.jl` - Multi-phase flow callable interface + +## Test Files + +- `test/suite/multiphase/test_multiphase_flow.jl` - Multi-phase flow tests +- `test/suite/multiphase/test_concatenation.jl` - Concatenation tests +- `test/suite/multiphase/test_calling.jl` - Multi-phase calling tests + +## See Also + +- [flows.md](flows.md) - Single-phase flow types +- [systems.md](systems.md) - System types used in flows +- [solutions.md](solutions.md) - Solution types returned by multi-phase flows diff --git a/reports/docs/solutions.md b/reports/docs/solutions.md new file mode 100644 index 00000000..1c1a6d77 --- /dev/null +++ b/reports/docs/solutions.md @@ -0,0 +1,72 @@ +# Solutions + +## Overview + +The Solutions module provides solution types and solution building functions for CTFlows. Solutions wrap raw ODE integration results and provide semantic accessors for state, costate, and time data. They also support plotting functionality through the RecipesBase interface. + +## Key Types + +### Abstract Types + +- `AbstractIntegrationResult` - Abstract type for raw ODE integration results (defined in Integrators module) + +### Concrete Types + +- `VectorFieldSolution{R, C}` - Solution type for state-only systems, wrapping an integration result R with configuration C +- `HamiltonianVectorFieldSolution{R, C}` - Solution type for Hamiltonian systems (state + costate), wrapping an integration result R with configuration C + +## Traits + +Solution types inherit their trait information from the configuration parameter C. The configuration encodes mode (PointTrait vs TrajectoryTrait) and content (StateTrait vs HamiltonianTrait) traits, which determine what data is stored and how it can be accessed. + +## Usage Pattern + +The typical usage pattern for solutions: + +1. A flow is called to perform integration (see [flows.md](flows.md)) +2. The flow returns an integration result (from the Integrators module) +3. The integration result is wrapped in a solution type using `build_solution` +4. Solution accessors provide semantic access to the data: + - `state(solution)` - Returns the state trajectory or final state + - `costate(solution)` - Returns the costate trajectory or final costate (Hamiltonian only) + - `time_grid(solution)` - Returns the time points +5. Solutions can be plotted using the `plot` function (RecipesBase integration) + +### Building Functions + +- `build_solution(result, config)` - Constructs a solution from an integration result and configuration + +### Accessors + +For VectorFieldSolution (state-only): +- `state(solution)` - Returns state data (trajectory or point depending on mode) +- `time_grid(solution)` - Returns time grid + +For HamiltonianVectorFieldSolution (state + costate): +- `state(solution)` - Returns state data +- `costate(solution)` - Returns costate data +- `time_grid(solution)` - Returns time grid + +### Plotting + +- `plot(solution)` - Plots the solution using RecipesBase, with automatic handling of state vs Hamiltonian and point vs trajectory modes + +Solutions serve as the result layer - they wrap raw integration results in a type-safe, semantically meaningful interface that aligns with the configuration used for integration. + +## Source Files + +- `src/Solutions/vector_field_solution.jl` - VectorFieldSolution implementation +- `src/Solutions/hamiltonian_vector_field_solution.jl` - HamiltonianVectorFieldSolution implementation +- `src/Solutions/building.jl` - Solution building functions + +## Test Files + +- `test/suite/solutions/test_vector_field_solution.jl` - VectorFieldSolution tests +- `test/suite/solutions/test_hamiltonian_vector_field_solution.jl` - HamiltonianVectorFieldSolution tests +- `test/suite/solutions/test_building.jl` - Solution building tests + +## See Also + +- [flows.md](flows.md) - How flows return integration results +- [integrators.md](integrators.md) - Integration result types +- [traits.md](traits.md) - Configuration traits that determine solution structure diff --git a/reports/docs/systems.md b/reports/docs/systems.md new file mode 100644 index 00000000..17134495 --- /dev/null +++ b/reports/docs/systems.md @@ -0,0 +1,74 @@ +# Systems + +## Overview + +The Systems module provides system types and contracts for dynamical systems in CTFlows. Systems represent fully assembled objects that can be integrated, embedding their own right-hand side (rhs) functions, dimensional metadata, and solution-building logic. Systems inherit traits from their underlying data structures (vector fields), enabling compile-time dispatch throughout the integration stack. + +## Key Types + +### Abstract Types + +- `AbstractSystem{TD, VD}` - Abstract base type for all systems with time dependence (TD) and variable dependence (VD) traits +- `AbstractStateSystem{TD, VD}` - Abstract type for state systems (non-Hamiltonian) +- `AbstractHamiltonianSystem{TD, VD}` - Abstract type for Hamiltonian systems (state + costate) + +### Concrete Types + +- `VectorFieldSystem{VF, TD, VD}` - Concrete system wrapping a VectorField +- `HamiltonianVectorFieldSystem{VF, TD, VD}` - Concrete system wrapping a HamiltonianVectorField + +## Traits + +Systems inherit their traits from the underlying vector field data structures: + +- **Time Dependence (TD)**: `Autonomous` or `NonAutonomous` - inherited from vector field +- **Variable Dependence (VD)**: `Fixed` or `NonFixed` - inherited from vector field + +Trait accessors are implemented at the abstract level: +- `time_dependence(sys::AbstractSystem)` - Returns the time dependence trait type +- `variable_dependence(sys::AbstractSystem)` - Returns the variable dependence trait type +- `has_time_dependence_trait(sys::AbstractSystem)` - Always returns true +- `has_variable_dependence_trait(sys::AbstractSystem)` - Always returns true + +## Usage Pattern + +The typical usage pattern for systems: + +1. Create a vector field (see [data.md](data.md)) +2. Build a system from the vector field using `build_system` +3. The system inherits traits from the vector field +4. The system provides an `rhs` function for ODE integration +5. Systems are then combined with integrators to create flows (see [flows.md](flows.md)) + +### Contract Methods + +All concrete systems must implement: + +- `rhs(system::AbstractSystem)` - Returns a function `(du, u, p, t) -> nothing` that fills `du` in place with the derivative +- `rhs_oop(system::AbstractSystem)` - Returns a function `(u, p, t) -> du` for out-of-place operations (used with immutable arrays like StaticArrays) + +### Building Functions + +- `build_system(vector_field)` - Constructs a system from a vector field, automatically handling trait inheritance + +Systems serve as the integration-ready layer - they encapsulate the dynamics in a form suitable for ODE solvers. + +## Source Files + +- `src/Systems/abstract_system.jl` - Abstract type definitions and contract methods +- `src/Systems/vector_field_system.jl` - VectorFieldSystem implementation +- `src/Systems/hamiltonian_vector_field_system.jl` - HamiltonianVectorFieldSystem implementation +- `src/Systems/building.jl` - System building functions + +## Test Files + +- `test/suite/systems/test_abstract_system.jl` - Abstract system tests +- `test/suite/systems/test_vector_field_system.jl` - VectorFieldSystem tests +- `test/suite/systems/test_hamiltonian_vector_field_system.jl` - HamiltonianVectorFieldSystem tests +- `test/suite/systems/test_building.jl` - System building tests + +## See Also + +- [data.md](data.md) - Vector field data structures used to build systems +- [flows.md](flows.md) - How systems are combined with integrators to create flows +- [traits.md](traits.md) - Trait system overview diff --git a/reports/docs/traits.md b/reports/docs/traits.md new file mode 100644 index 00000000..7f8c4bec --- /dev/null +++ b/reports/docs/traits.md @@ -0,0 +1,84 @@ +# Traits + +## Overview + +CTFlows uses a trait system for compile-time dispatch. Traits are empty marker types used as type parameters to encode configuration properties at compile time. Unlike tags (which mark extension implementations), traits encode semantic properties of the configuration itself. All concrete trait types are empty structs with no fields, making them zero-cost at runtime. + +The trait pattern enables static dispatch on configuration properties without runtime type checks, providing type stability and performance benefits. + +## Key Types + +### Abstract Trait Hierarchy + +- `AbstractTrait` - Base type for all trait markers +- `AbstractModeTrait` - Base for mode traits (Point vs Trajectory) +- `AbstractContentTrait` - Base for content traits (State vs Hamiltonian) + +### Mode Traits + +- `PointTrait` - Point integration mode (single endpoint evaluation) +- `TrajectoryTrait` - Trajectory integration mode (full time evolution) + +### Content Traits + +- `StateTrait` - State content (no costate) +- `HamiltonianTrait` - Hamiltonian content (state + costate) + +### Time Dependence (from CTModels.OCP) + +- `Autonomous` - Time-independent dynamics +- `NonAutonomous` - Time-dependent dynamics + +### Variable Dependence (from CTModels.OCP) + +- `Fixed` - Fixed parameters (non-variable) +- `NonFixed` - Variable parameters + +### Configuration Types + +- `AbstractConfig` - Base configuration type +- `AbstractPointConfig` - Point-mode configuration base +- `AbstractTrajectoryConfig` - Trajectory-mode configuration base +- `StatePointConfig` - State-only point configuration +- `StateTrajectoryConfig` - State-only trajectory configuration +- `HamiltonianPointConfig` - Hamiltonian point configuration +- `HamiltonianTrajectoryConfig` - Hamiltonian trajectory configuration + +## Traits + +Traits are used as type parameters in abstract configuration types: + +- Mode traits (second type parameter): distinguish integration modes +- Content traits (third type parameter): distinguish content types +- Time dependence (in data/systems/flows): encode autonomous vs non-autonomous +- Variable dependence (in data/systems/flows): encode fixed vs non-fixed + +## Usage Pattern + +Traits are used throughout CTFlows to enable compile-time dispatch: + +1. Configuration types use mode and content traits as type parameters to determine storage layout and behavior +2. Data types (vector fields) use time and variable dependence traits to encode dynamics properties +3. System types inherit traits from their underlying data structures +4. Flow types inherit traits from their systems +5. Trait accessors (e.g., `time_dependence`, `variable_dependence`) extract trait values from types + +This enables the compiler to generate specialized code for each trait combination without runtime checks. + +## Source Files + +- `src/Common/abstract_trait.jl` - Abstract trait definitions and concrete trait types +- `src/Common/configs.jl` - Configuration type definitions +- `src/Common/traits.jl` - Trait-related utilities + +## Test Files + +- `test/suite/common/test_abstract_trait.jl` - Trait type tests +- `test/suite/common/test_configs.jl` - Configuration type tests +- `test/suite/common/test_traits.jl` - Trait accessor tests + +## See Also + +- [data.md](data.md) - How traits are used in vector field data structures +- [systems.md](systems.md) - How traits propagate to system types +- [flows.md](flows.md) - How traits propagate to flow types diff --git a/reports/hamilonian/hvf_getter_spec.md b/reports/hamilonian/hvf_getter_spec.md new file mode 100644 index 00000000..b0f09fba --- /dev/null +++ b/reports/hamilonian/hvf_getter_spec.md @@ -0,0 +1,323 @@ +# Spรฉcification : getter `hamiltonian_vector_field` โ€” CTFlows.jl + +## 1. Contexte et motivation + +### 1.1 Architecture existante + +CTFlows.jl organise la construction d'un flot hamiltonien en trois niveaux : + +``` +Hamiltonian (donnรฉes pures : H(t, x, p[, v]) โ†’ โ„) + โ†“ +HamiltonianSystem (Hamiltonian + backend AD โ†’ RHS ODE) + โ†“ +HamiltonianFlow (HamiltonianSystem + intรฉgrateur โ†’ flot) +``` + +ร€ chaque niveau, les traits de type `TD โˆˆ {Autonomous, NonAutonomous}` et +`VD โˆˆ {Fixed, NonFixed}` encodent statiquement la dรฉpendance temporelle et +paramรฉtrรฉe du systรจme. + +### 1.2 Problรจme + +Le champ de vecteurs hamiltonien + +``` +X_H : (t, x, p[, v]) โ†ฆ (แบ‹, แน—) = (โˆ‚H/โˆ‚p, โˆ’โˆ‚H/โˆ‚x) +``` + +est dรฉjร  **calculรฉ en interne** lors de l'intรฉgration ODE (via `rhs` et +`rhs_oop` de `HamiltonianSystem`), mais il n'est pas exposรฉ ร  l'utilisateur +sous une forme directement exploitable. + +Un utilisateur souhaitant รฉvaluer `X_H` ponctuellement (pour du dรฉbogage, +de la visualisation, un test unitaire, ou une composition avec d'autres +outils) doit actuellement accรฉder aux internals du systรจme, ce qui est fragile +et non documentรฉ. + +### 1.3 Objectif + +Exposer `X_H` via un **getter public `hamiltonian_vector_field`** ร  trois +niveaux de la hiรฉrarchie, retournant un `HamiltonianVectorField` โ€” type +dรฉjร  existant dans `CTFlows.Data` โ€” portant les bons traits et les bonnes +signatures d'appel. + +--- + +## 2. Type de retour : `HamiltonianVectorField` + +Le type `HamiltonianVectorField{F, TD, VD, MD}` existe dรฉjร  dans `CTFlows.Data`. +Il porte : + +| Paramรจtre | Rรดle | +|:----------|:-----| +| `F <: Function` | type concret de la closure wrappรฉe | +| `TD <: TimeDependence` | `Autonomous` ou `NonAutonomous` | +| `VD <: VariableDependence` | `Fixed` ou `NonFixed` | +| `MD <: AbstractMutabilityTrait` | `InPlace` ou `OutOfPlace` | + +Il expose deux familles de signatures d'appel : + +**Out-of-place (OOP)** + +| Traits | Signature naturelle | Signature uniforme | +|:-------|:--------------------|:-------------------| +| Autonomous / Fixed | `Xh(x, p)` | `Xh(t, x, p, v)` | +| NonAutonomous / Fixed | `Xh(t, x, p)` | `Xh(t, x, p, v)` | +| Autonomous / NonFixed | `Xh(x, p, v)` | `Xh(t, x, p, v)` | +| NonAutonomous / NonFixed | `Xh(t, x, p, v)` | `Xh(t, x, p, v)` | + +**In-place (IP)** + +| Traits | Signature naturelle | Signature uniforme | +|:-------|:--------------------|:-------------------| +| Autonomous / Fixed | `Xh(dx, dp, x, p)` | `Xh(dx, dp, t, x, p, v)` | +| NonAutonomous / Fixed | `Xh(dx, dp, t, x, p)` | `Xh(dx, dp, t, x, p, v)` | +| Autonomous / NonFixed | `Xh(dx, dp, x, p, v)` | `Xh(dx, dp, t, x, p, v)` | +| NonAutonomous / NonFixed | `Xh(dx, dp, t, x, p, v)` | `Xh(dx, dp, t, x, p, v)` | + +Le getter produit la **closure appropriรฉe** puis l'enveloppe dans un +`HamiltonianVectorField` en passant `is_autonomous`, `is_variable` et +`is_inplace` dรฉduits des traits du systรจme source. + +--- + +## 3. Trois surcharges du getter + +### 3.1 `hamiltonian_vector_field(h::Hamiltonian; backend, inplace)` โ€” entrรฉe directe + +Un `Hamiltonian` seul ne porte pas de backend AD, mais grรขce ร  +`build_ad_backend()` et `DifferentiationInterface`, on peut en fournir un +directement au getter via un kwarg. + +**Signature proposรฉe :** + +```julia +function hamiltonian_vector_field( + h::Data.Hamiltonian{F, TD, VD}; + backend::Differentiation.AbstractADBackend = Differentiation.build_ad_backend(), + inplace::Bool = false +) where {F, TD, VD} +``` + +**Comportement :** construit la closure via `_make_oop_hvf` / `_make_ip_hvf` +directement depuis `h` et `backend`, sans passer par un `HamiltonianSystem` +intermรฉdiaire. C'est รฉquivalent ร  `hamiltonian_vector_field(HamiltonianSystem(h, backend); inplace)`, +mais sans allouer ni `rhs` ni `rhs_oop` qui ne servent pas ici. + +**Valeur par dรฉfaut du backend :** `build_ad_backend()` retourne un +`DifferentiationInterface(; prepare_cache=false)` avec `AutoForwardDiff()`. +L'utilisateur n'a rien ร  spรฉcifier dans le cas standard. + +**Exemples d'usage :** + +```julia +# Cas le plus simple โ€” backend par dรฉfaut (AutoForwardDiff) +h = Hamiltonian((x, p) -> 0.5 * sum(p.^2)) +Xh = hamiltonian_vector_field(h) + +# Backend explicite +Xh = hamiltonian_vector_field(h; backend = build_ad_backend(backend = AutoZygote())) + +# In-place +Xh = hamiltonian_vector_field(h; inplace = true) +``` + +**Justification de ce choix vs erreur :** exposer un backend par dรฉfaut +fonctionnel est plus ergonomique et cohรฉrent avec le reste de l'API +(e.g., `HamiltonianSystem` a aussi un backend par dรฉfaut implicite dans son +constructeur). L'erreur n'apporte rien quand un dรฉfaut sensรฉ existe. + +### 3.2 `hamiltonian_vector_field(sys::HamiltonianSystem; inplace=false)` โ€” surcharge systรจme + +C'est ici que le travail est fait. + +Dรฉlรจgue simplement ร  la surcharge sur `Hamiltonian` en passant le backend du systรจme : + +```julia +function hamiltonian_vector_field(sys::HamiltonianSystem{N,F,TD,VD}; inplace=false) where {N,F,TD,VD} + return hamiltonian_vector_field(sys.h; backend=sys.backend, inplace) +end +``` + +Toute la logique de construction des closures reste dans la surcharge `Hamiltonian`, +qui devient le **point d'entrรฉe canonique**. La surcharge `HamiltonianSystem` n'est +qu'une faรงade qui extrait le backend et dรฉlรจgue. + +**Pourquoi appeler `hamiltonian_gradient` directement plutรดt que `rhs_oop` ?** + +`rhs_oop` a la signature ODE interne `(u, ฮป, t)` oรน `ฮป` est un +`ODEParameters` (portant variable + cache). Utiliser `rhs_oop` dans le getter +crรฉerait une dรฉpendance forte ร  `ODEParameters`, type interne ร  ne pas +exposer dans une API publique. En appelant `hamiltonian_gradient` directement, +la closure est autonome et ne suppose rien sur la structure interne des +paramรจtres ODE. + +**Pourquoi `cache = nothing` ?** + +Le cache prรฉparรฉ (`DifferentiationInterfaceCache`) est conรงu pour +**l'intรฉgration ODE rรฉpรฉtรฉe** : il prรฉ-alloue les plans de diffรฉrentiation +pour des appels successifs avec des `(x, p)` de mรชme type et taille. + +Dans le contexte du getter, on suppose des **appels ponctuels** (dรฉbogage, +visualisation, tests). Passer `cache = nothing` dรฉclenche le fallback vers +`DI.gradient` sans plan prรฉparรฉ, ce qui est correct et sans surcoรปt notable +pour des appels isolรฉs. + +Pour des appels rรฉpรฉtรฉs en dehors du flow (cas avancรฉ), l'utilisateur peut +toujours intรฉgrer via `HamiltonianFlow` qui, lui, gรจre le cache. + +**kwarg `inplace::Bool` (dรฉfaut `false`) :** + +- `false` โ†’ closure OOP, retourne un tuple `(แบ‹, แน—)`. +- `true` โ†’ closure IP, remplit `dx` et `dp` en place, retourne `nothing`. + +Le dรฉfaut `false` est choisi car l'OOP est naturel pour les appels ponctuels +et correspond ร  la reprรฉsentation mathรฉmatique `X_H(t, x, p) = (แบ‹, แน—)`. + +**Hiรฉrarchie d'appel finale :** + +``` +hamiltonian_vector_field(flow) โ†’ hamiltonian_vector_field(flow.system) +hamiltonian_vector_field(sys) โ†’ hamiltonian_vector_field(sys.h; backend=sys.backend, ...) +hamiltonian_vector_field(h; backend, inplace) โ† point d'entrรฉe canonique + โ†“ +_make_oop_hvf / _make_ip_hvf(h, backend, TD, VD) + โ†“ +HamiltonianVectorField(wrapped; is_autonomous, is_variable, is_inplace) +``` + +### 3.4 `hamiltonian_vector_field(flow::HamiltonianFlow; inplace=false)` โ€” dรฉlรฉgation + +Dรฉlรจgue ร  la surcharge sur `HamiltonianSystem` : + +```julia +function hamiltonian_vector_field(flow::HamiltonianFlow; inplace=false) + return hamiltonian_vector_field(flow.system; inplace) +end +``` + +**Justification :** le `HamiltonianFlow` n'ajoute rien au calcul de `X_H` ; +l'intรฉgrateur n'intervient pas. La dรฉlรฉgation garde toute la logique dans +la surcharge `Hamiltonian`, sans dupliquer de code. + +--- + +## 4. Helpers internes `_make_oop_hvf` / `_make_ip_hvf` + +Huit fonctions (4 combinaisons de traits ร— 2 variantes OOP/IP), toutes dans +`Systems`, non exportรฉes. + +Elles dispatchรฉ sur `(::Type{TD}, ::Type{VD})` โ€” dispatch de type, pas de +valeur โ€” pour que le compilateur gรฉnรจre du code spรฉcialisรฉ par combinaison de +traits sans branchement ร  l'exรฉcution. + +**Pattern OOP :** + +```julia +_make_oop_hvf(h, backend, ::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = + (x, p) -> begin + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, 0.0, x, p, nothing, nothing) + return (โˆ‚p, -โˆ‚x) + end +``` + +**Note sur `0.0` pour les systรจmes autonomes :** le `Hamiltonian` autonome +appelle `H.f(x, p)` via sa signature uniforme `H(_, x, p, _)` โ€” `t` est +ignorรฉ. Passer `0.0` (plutรดt que `nothing`) garantit la stabilitรฉ de type +pour les backends AD, qui peuvent inspecter le type de tous les arguments +mรชme si certains sont des `Constant`. + +**Pattern IP :** + +```julia +_make_ip_hvf(h, backend, ::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = + (dx, dp, x, p) -> begin + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, 0.0, x, p, nothing, nothing) + dx .= โˆ‚p; dp .= .-โˆ‚x + return nothing + end +``` + +`dx .= โˆ‚p` et `dp .= .-โˆ‚x` utilisent la diffusion Julia pour remplir les +buffers en place sans allocation intermรฉdiaire. + +--- + +## 5. Localisation dans le dรฉpรดt + +| Composant | Fichier suggรฉrรฉ | +|:----------|:----------------| +| `hamiltonian_vector_field(::Hamiltonian)` โ€” point d'entrรฉe canonique | `src/data/hamiltonian.jl` ou `src/systems/hamiltonian_system.jl` | +| `_make_oop_hvf` / `_make_ip_hvf` | mรชme fichier, helpers privรฉs | +| `hamiltonian_vector_field(::HamiltonianSystem)` | `src/systems/hamiltonian_system.jl` | +| `hamiltonian_vector_field(::HamiltonianFlow)` | `src/flows/hamiltonian_flow.jl` | +| Export public | `src/CTFlows.jl` (ou module `Systems` / `Flows`) | + +--- + +## 6. Tests ร  รฉcrire + +### 6.1 Sur `Hamiltonian` seul avec backend par dรฉfaut +- Vรฉrifier que `hamiltonian_vector_field(h)` fonctionne sans rien spรฉcifier + (utilise `build_ad_backend()` โ†’ `AutoForwardDiff`). +- Vรฉrifier que `hamiltonian_vector_field(h; backend=build_ad_backend(backend=AutoZygote()))` + utilise bien le backend spรฉcifiรฉ. +- Vรฉrifier que le backend par dรฉfaut peut รชtre surchargรฉ globalement si + `CTSolvers.Strategies` le permet. + +### 6.2 Sur `HamiltonianSystem` โ€” OOP +Pour chaque combinaison `(TD, VD)` : +- Construire un hamiltonien analytique dont les gradients sont connus. +- Appeler `Xh = hamiltonian_vector_field(sys)` et vรฉrifier le retour avec la + signature naturelle et la signature uniforme. +- Vรฉrifier que `Xh` est bien un `HamiltonianVectorField{..., OutOfPlace}`. + +**Exemple canonique** (`Autonomous/Fixed`, `H(x,p) = ยฝโ€–pโ€–ยฒ`) : +``` +X_H(x, p) = (โˆ‚H/โˆ‚p, โˆ’โˆ‚H/โˆ‚x) = (p, 0) +``` + +### 6.3 Sur `HamiltonianSystem` โ€” IP +- Idem, vรฉrifier que les buffers sont correctement remplis. +- Vรฉrifier le type `HamiltonianVectorField{..., InPlace}`. +- Vรฉrifier que la valeur de retour est `nothing`. + +### 6.4 Sur `HamiltonianFlow` +- Vรฉrifier que `hamiltonian_vector_field(flow)` retourne le mรชme objet + (ou un objet รฉquivalent) que `hamiltonian_vector_field(flow.system)`. + +### 6.5 NonFixed +- Vรฉrifier que pour `VD = NonFixed`, la closure accepte bien `v` et que + `โˆ‚H/โˆ‚x` et `โˆ‚H/โˆ‚p` dรฉpendent correctement de `v`. + +### 6.6 Backends +- Tester avec au moins `AutoForwardDiff()` (ou l'รฉquivalent disponible). +- Optionnel : tester que `cache = nothing` ne pose pas de problรจme de + performance mesurable sur un appel unique. + +--- + +## 7. Points d'attention et limites connues + +| Point | Dรฉtail | +|:------|:-------| +| **Cache non utilisรฉ** | Les appels via le getter sont sans cache. Pour des simulations intensives, passer par le flow. | +| **NonFixed et `variable_gradient`** | Le getter ne retourne pas `vฬ‡ = โˆ’โˆ‚H/โˆ‚v`. Si le besoin รฉmerge, prรฉvoir une variante `augmented=true` retournant `(แบ‹, แน—, vฬ‡)`. | +| **Type de `t` pour systรจmes autonomes** | `0.0` est hardcodรฉ. Si des backends exigent un type entier ou symbolique pour `t`, la closure devrait accepter `t` comme argument supplรฉmentaire ignorรฉ. | +| **Stabilitรฉ de type** | La closure doit รชtre type-stable pour que `hamiltonian_gradient` le soit aussi. ร€ vรฉrifier avec `@code_warntype` sur les combinaisons OOP. | + +--- + +## 8. Rรฉsumรฉ dรฉcisionnel + +| Question | Dรฉcision | Raison | +|:---------|:---------|:-------| +| Rรฉutiliser `rhs_oop` ? | โœ— Non | ร‰vite la dรฉpendance ร  `ODEParameters` | +| Utiliser `hamiltonian_gradient` directement ? | โœ“ Oui | API publique, stable, claire | +| `cache = nothing` ? | โœ“ Oui | Getter = usage ponctuel hors intรฉgration | +| Retour OOP = tuple `(แบ‹, แน—)` ? | โœ“ Oui | Reprรฉsentation mathรฉmatique naturelle | +| kwarg `inplace` ? | โœ“ Oui, dรฉfaut `false` | OOP = cas par dรฉfaut ; IP disponible | +| **Backend par dรฉfaut sur `Hamiltonian`** | `build_ad_backend()` โ†’ `AutoForwardDiff`, `prepare_cache=false` | Zรฉro config pour l'utilisateur | +| Erreur sur `Hamiltonian` seul ? | โœ— Non โ€” backend par dรฉfaut | `build_ad_backend()` rend l'erreur inutile | +| `vฬ‡` dans le retour ? | โœ— Non (pour l'instant) | Hors scope ; prรฉvoir extension future | diff --git a/reports/hamilonian/save/flux_map.md b/reports/hamilonian/save/flux_map.md new file mode 100644 index 00000000..449ce0a4 --- /dev/null +++ b/reports/hamilonian/save/flux_map.md @@ -0,0 +1,156 @@ +# Carte des Flux CTFlows + +## Vue d'ensemble des 3 couches + +```text +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ LAYER 1: DATA โ”‚ +โ”‚ (Fonctions brutes avec traits de dรฉpendance) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + build_system() + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ LAYER 2: SYSTEMS โ”‚ +โ”‚ (RHS prรฉ-calculรฉ, prรชt pour intรฉgration) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + build_flow() + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ LAYER 3: FLOWS โ”‚ +โ”‚ (System + Integrator, callable pour intรฉgration) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Hiรฉrarchie des Types + +### Couche DATA + +```text +AbstractVectorField{TD, VD, MD} +โ”œโ”€โ”€ VectorField{F, TD, VD, MD} +โ”‚ โ””โ”€โ”€ f::Function (retourne dx) +โ”‚ +โ””โ”€โ”€ HamiltonianVectorField{F, TD, VD, MD} + โ””โ”€โ”€ f::Function (retourne (dx, dp)) +``` + +**Traits:** + +- `TD`: TimeDependence (Autonomous | NonAutonomous) +- `VD`: VariableDependence (Fixed | NonFixed) +- `MD`: MutabilityTrait (InPlace | OutOfPlace) + +### Couche SYSTEMS + +```text +AbstractSystem{TD, VD} +โ”œโ”€โ”€ AbstractStateSystem{TD, VD} +โ”‚ โ””โ”€โ”€ StateSystem{F, TD, VD, MD, RHS, OOPROHS, FINRHS} +โ”‚ โ”œโ”€โ”€ vf::VectorField +โ”‚ โ”œโ”€โ”€ rhs::Function (prรฉ-calculรฉ) +โ”‚ โ”œโ”€โ”€ rhs_oop::Function +โ”‚ โ””โ”€โ”€ rhs_oop_finalize::Function +โ”‚ +โ””โ”€โ”€ AbstractHamiltonianSystem{TD, VD} + โ””โ”€โ”€ HamiltonianSystem{N, F, TD, VD, MD, RHS, OOPROHS, FINRHS} + โ”œโ”€โ”€ hvf::HamiltonianVectorField + โ”œโ”€โ”€ rhs::Function (prรฉ-calculรฉ, split x/p) + โ”œโ”€โ”€ rhs_oop::Function + โ””โ”€โ”€ rhs_oop_finalize::Function +``` + +### Couche FLOWS + +```text +AbstractFlow{TD, VD} +โ”œโ”€โ”€ AbstractStateFlow{TD, VD, S} +โ”‚ โ””โ”€โ”€ StateFlow{TD, VD, S, I} +โ”‚ โ”œโ”€โ”€ system::S (StateSystem) +โ”‚ โ””โ”€โ”€ integrator::I +โ”‚ +โ””โ”€โ”€ AbstractHamiltonianFlow{TD, VD, S} + โ””โ”€โ”€ HamiltonianFlow{TD, VD, S, I} + โ”œโ”€โ”€ system::S (HamiltonianSystem) + โ””โ”€โ”€ integrator::I +``` + +## Flux de Transformation + +### Chemin 1: VectorField โ†’ StateSystem โ†’ StateFlow + +```text +VectorField(f; is_autonomous, is_variable, is_inplace) + โ”‚ + โ”œโ”€ build_system(vf) + โ”‚ โ””โ”€> StateSystem(vf) + โ”‚ โ”œโ”€โ”€ Construit rhs: (du, u, p, t) -> vf(t, u, p.variable) + โ”‚ โ”œโ”€โ”€ Construit rhs_oop: (u, p, t) -> vf(t, u, p.variable) + โ”‚ โ””โ”€โ”€ Stocke le VectorField + โ”‚ + โ”œโ”€ build_flow(system, integrator) + โ”‚ โ””โ”€> StateFlow(system, integrator) + โ”‚ โ”œโ”€โ”€ Stocke le StateSystem + โ”‚ โ”œโ”€โ”€ Stocke l'Integrator + โ”‚ โ””โ”€ Callable: flow(t0, x0, tf; variable) + โ”‚ + โ””โ”€> Rรฉsultat: Flow prรชt pour intรฉgration +``` + +### Chemin 2: HamiltonianVectorField โ†’ HamiltonianSystem โ†’ HamiltonianFlow + +```text +HamiltonianVectorField(f; is_autonomous, is_variable, is_inplace) + โ”‚ + โ”œโ”€ build_system(hvf) [sans dimension] + โ”‚ โ””โ”€> HamiltonianSystem(hvf) (N=nothing) + โ”‚ โ”œโ”€โ”€ Construit rhs: split u en (x,p), appelle hvf, concatรจne (dx,dp) + โ”‚ โ”œโ”€โ”€ Dimension infรฉrรฉe ร  l'exรฉcution + โ”‚ โ””โ”€โ”€ Stocke le HamiltonianVectorField + โ”‚ + โ”œโ”€ build_system(hvf, state_dimension) [avec dimension] + โ”‚ โ””โ”€> HamiltonianSystem(hvf, state_dimension) (N=state_dimension) + โ”‚ โ”œโ”€โ”€ Construit rhs avec N connu (type-stable) + โ”‚ โ”œโ”€โ”€ Validation compile-time de la dimension + โ”‚ โ””โ”€โ”€ Stocke le HamiltonianVectorField + โ”‚ + โ”œโ”€ build_flow(system, integrator) + โ”‚ โ””โ”€> HamiltonianFlow(system, integrator) + โ”‚ โ”œโ”€โ”€ Stocke le HamiltonianSystem + โ”‚ โ”œโ”€โ”€ Stocke l'Integrator + โ”‚ โ””โ”€ Callable: flow(t0, x0, p0, tf; variable) + โ”‚ + โ””โ”€> Rรฉsultat: Flow prรชt pour intรฉgration +``` + +## Constructeurs de Haut Niveau + +Les constructeurs `Flow()` dans `Flows/building.jl` combinent toutes les รฉtapes: + +```julia +# Pour les systรจmes d'รฉtat +Flow(vf::VectorField; opts...) + = build_flow(build_system(vf), build_integrator(; opts...)) + +# Pour les systรจmes hamiltoniens (sans dimension) +Flow(hvf::HamiltonianVectorField; opts...) + = build_flow(build_system(hvf), build_integrator(; opts...)) + +# Pour les systรจmes hamiltoniens (avec dimension) +Flow(hvf::HamiltonianVectorField, state_dimension::Int; opts...) + = build_flow(build_system(hvf, state_dimension), build_integrator(; opts...)) +``` + +## Rรฉsumรฉ des Transformations + +| ร‰tape | Entrรฉe | Fonction | Sortie | Ce qui se passe | +| ----- | ------ | --------- | ------ | ---------------- | +| 1 | `VectorField` | `build_system()` | `StateSystem` | Enveloppe le champ de vecteur, prรฉ-calcule les RHS | +| 2 | `HamiltonianVectorField` | `build_system()` | `HamiltonianSystem` | Enveloppe, split x/p, prรฉ-calcule les RHS | +| 3 | `StateSystem` + `Integrator` | `build_flow()` | `StateFlow` | Combine systรจme et intรฉgrateur en objet callable | +| 4 | `HamiltonianSystem` + `Integrator` | `build_flow()` | `HamiltonianFlow` | Combine systรจme et intรฉgrateur en objet callable | + +**Key insight:** Les RHS sont prรฉ-calculรฉs ร  la construction du `System` pour performance. Le `Flow` ne fait que combiner le `System` avec un `Integrator` et exposer l'interface callable. diff --git a/reports/hamilonian/save/hamiltonian-vector-field.md b/reports/hamilonian/save/hamiltonian-vector-field.md new file mode 100644 index 00000000..51b293de --- /dev/null +++ b/reports/hamilonian/save/hamiltonian-vector-field.md @@ -0,0 +1,51 @@ +@vector_field.jl#L1-217 Nous avons ici un VectorField. J'aimerais ajouter un HamiltonianVectorField. + +Soit Hv, un HamiltonianVectorField. Alors, on a la signature : + +``` +dx, dp = Hv([t ,] x, p[, v]) +``` + +ร€ partir d'un HamiltonianVectorField, on pourra crรฉer un System : + +@abstract_system.jl#L1-239 +@building.jl#L1-38 +@vector_field_system.jl#L1-126 + +Il faudra sรปrement crรฉer un HamiltonianSystem. + +A partir d'un HamiltonianSystem, il faudra pouvoir crรฉer un flot hamiltonien, qui en principe existe dรฉjร  : + +@abstract_flow.jl#L1-247 + @flow.jl#L131-134 ร  comparer avec @flow.jl#L101-104. + +Il faudra aussi pouvoir construire un flot, directement depuis le HamiltonianVectorField, comme on fait de maniรจre analogue @building.jl#L29-34 . + +Bien entendu, ร  partir d'un flot, qu'il soit hamiltonien ou non, on pourra l'appeler : @calling.jl#L1-46. J'espรจre que c'est suffisamment gรฉnรฉrique, la fonction call que il n'y aura pas grand chose ร  changer. + +Pour intรฉgrer un flot, nous avons besoin d'un intรฉgrateur : @abstract_integrator.jl#L1-149 @sciml.jl#L1-171. Un intรฉgrateur est une Strategy et doit donc implรฉmenter certaines mรฉthodes en ce sens, ce qu'il fait dรฉjร . Mais c'est donc aussi un intรฉgrateur. Et cela doit รชtre clair, je pense, qu'il y a des fonctions qui peuvent avoir plusieurs mรฉthodes en fonction du fait d'avoir un systรจme hamiltonien ou pas. Cela peut aussi passer par la config, car quand on a un systรจme hamiltonien, on va utiliser une config hamiltonienne @configs.jl#L1-625 . @CTFlowsSciML.jl#L1-592 : je n'ai pas l'impression qu'il y ait de distinction ร  ce niveau lร . @integration_result.jl#L1-93 non plus ici. Peut-รชtre au niveau des solutions, car en effet, une fois intรฉgrรฉ le flot, il faut retourner une solution. + +Il faudra gรฉrer la sortie : + +@building.jl#L1-67 + +En effet, si on a un flot hamiltonien f, on voudra faire soit + +``` +xf, pf = f(t0, x0, p0, tf) +``` + +soit + +``` +flow_sol = f((t0, tf), x0, p0) +``` + +avec la variable si le flot est variable. + +Remarque : on pourra se poser la question de la nรฉcessitรฉ d'avoir dans @building.jl#L23-24 @building.jl#L44-45 @building.jl#L64-65 ร  la fois le systรจme et la config, qui doivent รชtre les deux cohรฉrents, car si on a un systรจme hamiltonien, il faudra une config hamiltonien. Il est ร  noter que plus tard, nous aurons des flots construits ร  partir d'un problรจme de contrรดle optimal, le flot hamiltonien et on voudra faire xf, pf = f(t0, x0, p0, tf) mais dans la version f((t0, tf), x0, p0) la solution retournรฉe aura un type particulier : CTModels.Solution. Ce sera en fait une solution d'un problรจme de contrรดle optimal. + +Remarque : pour รชtre trรจs cohรฉrent, doit-on appeler les configs StatePointConfig et StateTrajectoryConfig : StateStatePointConfig et StateStateTrajectoryConfig ? + +Pour crรฉer une solution particuliรจre, on aura besoin de crรฉer l'analogue de @vector_field_solution.jl#L1-291 . De maniรจre รฉquivalent, il y aura ร  gรฉrer l'affichage mais aussi @CTFlowsPlots.jl#L1-101 . On pourra simplement faire un layout (1, 2) pour l'รฉtat et le co-รฉtat. + diff --git a/reports/hamilonian/save/hamiltonian_plan.md b/reports/hamilonian/save/hamiltonian_plan.md new file mode 100644 index 00000000..d4dd0ce8 --- /dev/null +++ b/reports/hamilonian/save/hamiltonian_plan.md @@ -0,0 +1,1017 @@ +# Hamiltonian Type โ€” Implementation Plan + +## Context + +CTFlows.jl (`develop` branch) currently supports two entry points for constructing Hamiltonian flows: + +- `HamiltonianVectorField` โ€” the user provides the vector field `(แบ‹, แน—) = F(t, x, p[, v])` directly. +- `HamiltonianVectorFieldSystem` โ€” wraps a `HamiltonianVectorField` and pre-builds in-place and out-of-place RHS closures. +- `HamiltonianFlow` โ€” wraps a `HamiltonianVectorFieldSystem` and an `AbstractIntegrator`. + +The naming convention in `develop` is already consistent: + +| `Data` | `Systems` | +|---|---| +| `VectorField` | `VectorFieldSystem` | +| `HamiltonianVectorField` | `HamiltonianVectorFieldSystem` | +| `Hamiltonian` *(new)* | `HamiltonianSystem` *(new)* | + +There is no way today to provide a **scalar Hamiltonian function** `H(t, x, p[, v]) โ†’ โ„` and have the vector field derived automatically via automatic differentiation. The user must compute `โˆ‚H/โˆ‚p` and `โˆ‚H/โˆ‚x` manually. + +## Objectives + +1. Add a `Hamiltonian` scalar type in `Data`, parallel to `HamiltonianVectorField`. +2. Add a `Differentiation` module with an `AbstractADBackend` strategy and its contract. +3. Add an `AbstractADTrait` in `Common` to encode whether a system carries an AD backend. +4. Add `HamiltonianSystem` built from a `Hamiltonian` + backend, with RHS closures that use the AD backend and a runtime cache passed through `ODEParameters`. +5. Keep `HamiltonianFlow` structurally unchanged; route cache preparation via a `prepare_cache` function using three-layer trait dispatch (`ad_trait`). +6. Support `augment=true` on point calls for `NonFixed` Hamiltonian flows, computing the variable costate `pv(tf) = -โˆซ โˆ‚H/โˆ‚v dt` without zero dynamics. +7. Provide a high-level `Flow(h::Hamiltonian; ...)` constructor. + +## Dependency Graph After Changes + +``` +Common (traits incl. AbstractADTrait, AbstractCache, ODEParameters) + โ†“ +Data (Hamiltonian, HamiltonianVectorField, VectorField) + โ†“ +Differentiation (AbstractADBackend <: CTSolvers strategy, stubs) + โ†“ +Systems (HamiltonianVectorFieldSystem [WithoutAD], HamiltonianSystem [WithAD]) + โ†“ +Flows (HamiltonianFlow, prepare_cache trait dispatch, augment=true) + โ†“ +Solutions + โ†“ +ext/CTFlowsDifferentiationInterface (concrete cache, gradient implementations) +``` + +--- + +## Phase 1 โ€” New Trait and Cache Foundation in `Common` + +### Step 1 โ€” `src/Common/abstract_trait.jl` (modified) + +Add a new trait family for AD capability alongside the existing traits: + +```julia +abstract type AbstractADTrait <: AbstractTrait end +struct WithAD <: AbstractADTrait end # system carries H + AD backend +struct WithoutAD <: AbstractADTrait end # system carries HVF directly +``` + +Add `AbstractCache` as a common abstract type (parallel to `AbstractTag`, `AbstractTrait`): + +```julia +abstract type AbstractCache end +``` + +Add a new content trait for augmented integration (used by `AugmentedHamiltonianPointConfig` โ€” see Step 24a): + +```julia +struct AugmentedHamiltonianTrait <: ContentTrait end +``` + +Export: `AbstractADTrait`, `WithAD`, `WithoutAD`, `AbstractCache`, `AugmentedHamiltonianTrait`. + +### Step 2 โ€” `src/Common/ode_parameters.jl` (modified) + +Extend `ODEParameters` with an optional cache field: + +```julia +struct ODEParameters{V, C<:Union{AbstractCache, Nothing}} + variable::V + cache::C +end +``` + +Add a convenience constructor `ODEParameters(variable) = ODEParameters(variable, nothing)` to keep all existing call sites unchanged. + +Add a getter for the cache: + +```julia +function cache(p::ODEParameters) + return p.cache +end +``` + +### Step 3 โ€” Test Checkpoint: Common traits and ODEParameters + +- `@testset "Unit: WithAD / WithoutAD construction"` โ€” subtypes of `AbstractADTrait` +- `@testset "Unit: AbstractCache abstract type"` โ€” cannot be instantiated +- `@testset "Unit: ODEParameters with cache"` โ€” both constructors work, cache field accessible +- `@testset "Unit: ODEParameters backward compat"` โ€” single-arg constructor gives `cache=nothing` +- `@testset "Unit: AugmentedHamiltonianTrait construction"` โ€” subtype of `ContentTrait` +- `@testset "Exports"` โ€” all new names exported from `Common` + +--- + +## Phase 2 โ€” `Hamiltonian` Type in `Data` + +### Step 4 โ€” `src/Data/hamiltonian.jl` (new file) + +Define a scalar Hamiltonian type, independent of `AbstractVectorField` (a Hamiltonian is not a vector field): + +```julia +abstract type AbstractHamiltonian{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence +} end + +struct Hamiltonian{F<:Function, TD, VD} <: AbstractHamiltonian{TD, VD} + f::F +end +``` + +- Out-of-place only (a scalar return has no meaningful in-place form). +- Trait accessors `time_dependence`, `variable_dependence` implemented at the abstract level. +- Constructor `Hamiltonian(f; is_autonomous, is_variable)` with Bool flags and defaults (no auto-detection). + - Defaults from `Common.__is_autonomous()` and `Common.__is_variable()`. +- Natural call signatures: 4 combinations (Autonomous/NonAutonomous ร— Fixed/NonFixed). +- Uniform call signature `(t, x, p, v)` forwarding to the natural signature. +- `Base.show`. + +### Step 5 โ€” `src/Data/Data.jl` (modified) + +- Add `include("hamiltonian.jl")` after `hamiltonian_vector_field.jl`. +- Export `AbstractHamiltonian`, `Hamiltonian`. + +### Step 6 โ€” Test Checkpoint: `Data.Hamiltonian` + +- `@testset "Unit: Construction with all trait combinations"` +- `@testset "Unit: Natural call signatures"` โ€” all 4 combinations +- `@testset "Unit: Uniform call signature (t, x, p, v)"` โ€” all 4 combinations +- `@testset "Unit: Type stability"` โ€” uniform call type stability +- `@testset "Exports"` โ€” `AbstractHamiltonian`, `Hamiltonian` exported from `Data` + +--- + +## Phase 3 โ€” `Differentiation` Module and `AbstractADBackend` + +### Step 7 โ€” `src/Differentiation/abstract_ad_backend.jl` (new file) + +On the exact model of `Integrators/abstract_integrator.jl`: + +```julia +abstract type AbstractADBackend <: CTSolvers.Strategies.AbstractStrategy end +``` + +The contract is defined by three stubs (all throw `NotImplemented`): + +```julia +# Returns (โˆ‚H/โˆ‚x, โˆ‚H/โˆ‚p) โ€” raw partial derivatives, not negated. +# The negation (แน— = -โˆ‚H/โˆ‚x) is the RHS's responsibility, not the backend's. +function hamiltonian_gradient(backend::AbstractADBackend, h, t, x, p, v, cache=nothing) + throw(NotImplemented(...)) +end + +# Returns โˆ‚H/โˆ‚v โ€” raw partial derivative, not negated. +function variable_gradient(backend::AbstractADBackend, h, t, x, p, v, cache=nothing) + throw(NotImplemented(...)) +end + +# Returns a concrete AbstractCache built from typical values. +function prepare_cache(backend::AbstractADBackend, h, typical_x, typical_p, typical_v) + throw(NotImplemented(...)) +end +``` + +Design notes: + +- `cache` defaults to `nothing` so calls without a prepared cache still work. +- Gradients are returned **non-negated**; the RHS closures apply the signs. +- `prepare_cache` returns a `Common.AbstractCache` subtype; the concrete type is extension-specific. + +### Step 8 โ€” `src/Differentiation/differentiation_interface.jl` (new file) + +Concrete strategy wrapping DifferentiationInterface.jl backends: + +```julia +struct DifferentiationInterface{O<:CTSolvers.Strategies.StrategyOptions} <: AbstractADBackend + options::O +end +``` + +Implements `CTSolvers.Strategies.id`, `description`, `metadata` for the strategy contract. +`ADTypes` is a hard dependency (`[deps]`), so `AutoForwardDiff` is always available. +The `:backend` option definition uses `AutoForwardDiff()` directly as the default: + +```julia +default = AutoForwardDiff() # ADTypes.jl โ€” always available (hard dep) +``` + +No tag/stub pattern needed โ€” unlike `SciML` where the default algorithm (`Tsit5()`) comes +from a substantial optional package, `AutoForwardDiff()` is a zero-cost type from the +lightweight `ADTypes.jl` hard dep. + +### Step 9 โ€” `src/Differentiation/building.jl` (new file) + +```julia +function build_ad_backend(; kwargs...) + return DifferentiationInterface(; kwargs...) +end +``` + +Parallel to `Integrators.build_integrator`. + +### Step 10 โ€” `src/Differentiation/Differentiation.jl` (new file) + +Module manifest: imports, includes in order, exports. + +External imports include `using ADTypes: ADTypes` (hard dep โ€” provides `AutoForwardDiff`). + +Exports: `AbstractADBackend`, `DifferentiationInterface`, `build_ad_backend`, +`hamiltonian_gradient`, `variable_gradient`, `prepare_cache`. + +### Step 11 โ€” `src/CTFlows.jl` (modified) + +Add `include("Differentiation/Differentiation.jl")` and `using .Differentiation` after `Data`, before `Systems`. + +### Step 12 โ€” Test Checkpoint: `Differentiation` module + +- `@testset "Unit: DifferentiationInterface construction"` โ€” `DifferentiationInterface()` works; default `:backend` is `AutoForwardDiff()` +- `@testset "Unit: CTSolvers.Strategies contract"` โ€” `id`, `description`, `metadata` +- `@testset "Unit: build_ad_backend"` +- `@testset "Error: hamiltonian_gradient stub throws NotImplemented"` +- `@testset "Error: variable_gradient stub throws NotImplemented"` +- `@testset "Error: prepare_cache stub throws NotImplemented"` +- `@testset "Exports"` โ€” all names exported + +--- + +## Phase 4 โ€” `HamiltonianSystem` in `Systems` + +### Step 13 โ€” `src/Systems/abstract_system.jl` (modified) + +Add the AD trait parameter to the Hamiltonian system hierarchy: + +```julia +abstract type AbstractHamiltonianSystem{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + AT <: Common.AbstractADTrait +} <: AbstractSystem{TD, VD} end +``` + +Add trait accessor: + +```julia +Common.ad_trait(::AbstractHamiltonianSystem{TD, VD, AT}) where {TD, VD, AT} = AT +``` + +Update `HamiltonianVectorFieldSystem` parent type to `AbstractHamiltonianSystem{TD, VD, WithoutAD}`. + +### Step 14 โ€” `src/Systems/hamiltonian_system.jl` (new file) + +`HamiltonianSystem` is the new type built from a scalar `Hamiltonian` and an AD backend. +It carries `WithAD` and pre-builds RHS closures that read `cache(ฮป)` at each ODE step. + +```julia +struct HamiltonianSystem{ + N, + F <: Function, + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + BACKEND <: Differentiation.AbstractADBackend, + RHS <: Function, + OOPROHS <: Function, +} <: AbstractHamiltonianSystem{TD, VD, WithAD} + h::Data.Hamiltonian{F, TD, VD} + backend::BACKEND + rhs::RHS + rhs_oop::OOPROHS +end +``` + +**RHS construction** โ€” captures `h` and `backend`; reads `cache(ฮป)` at each step: + +```julia +function _build_rhs(h, backend, ::Val{N}) where N + return function (du, u, ฮป, t) + x, p = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, t, x, p, variable(ฮป), cache(ฮป)) + _ham_assign!(du, โˆ‚p, -โˆ‚x, N) # แบ‹ = โˆ‚H/โˆ‚p, แน— = -โˆ‚H/โˆ‚x + return nothing + end +end +``` + +**Augmented RHS** โ€” built lazily (not stored in the struct) using concrete dimensions +provided at integration time. This avoids the unresolvable split when `N = nothing`: + +```julia +function build_rhs_augmented(sys::HamiltonianSystem, n_x::Int, n_v::Int) + h, backend = sys.h, sys.backend + return function (du, u, ฮป, t) + x = u[1:n_x] + p = u[n_x+1:2*n_x] + pv = u[end-n_v+1:end] + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, t, x, p, variable(ฮป), cache(ฮป)) + โˆ‚v = Differentiation.variable_gradient(backend, h, t, x, p, variable(ฮป), cache(ฮป)) + du[1:n_x] .= โˆ‚p # แบ‹ = โˆ‚H/โˆ‚p + du[n_x+1:2*n_x] .= .-โˆ‚x # แน— = -โˆ‚H/โˆ‚x + du[end-n_v+1:end] .= .-โˆ‚v # แน—v = -โˆ‚H/โˆ‚v + return nothing + end +end +``` + +`n_x` and `n_v` are closed over as concrete integers, so the split is always exact +regardless of whether `N` is `nothing` or a static value. + +Accessors `rhs`, `rhs_oop`, `state_dimension` follow the same pattern as +`HamiltonianVectorFieldSystem`. `build_rhs_augmented(sys, n_x, n_v)` is the new entry point +for augmented integration โ€” called from `build_problem` (Step 22), not from constructors. + +### Step 15 โ€” `src/Systems/building.jl` (modified) + +Add factory functions for `HamiltonianSystem`: + +```julia +function build_system(h::Data.AbstractHamiltonian, backend::Differentiation.AbstractADBackend) + return HamiltonianSystem(h, backend) +end + +function build_system(h::Data.AbstractHamiltonian, state_dimension::Int, backend::Differentiation.AbstractADBackend) + return HamiltonianSystem(h, backend, state_dimension) +end +``` + +### Step 16 โ€” `src/Systems/Systems.jl` (modified) + +Add `include("hamiltonian_system.jl")`. Export `HamiltonianSystem`. + +### Step 17 โ€” Test Checkpoint: `HamiltonianSystem` + +- `@testset "Unit: Construction with/without state_dimension"` +- `@testset "Unit: ad_trait returns WithAD"` +- `@testset "Unit: build_rhs_augmented returns correct closure"` โ€” dimensions `n_x`, `n_v` respected +- `@testset "Unit: build_rhs_augmented raises on Fixed system"` โ€” contract error +- `@testset "Unit: build_system factory functions"` +- `@testset "Type Stability"` โ€” `@inferred` on constructors + +--- + +## Phase 5 โ€” Extension `CTFlowsDifferentiationInterface` + +### Step 18 โ€” `ext/CTFlowsDifferentiationInterface.jl` (new file) + +Define the concrete cache type: + +```julia +struct DifferentiationInterfaceCache{PX, PP, PV} <: Common.AbstractCache + prep_x::PX # prepared gradient for โˆ‚H/โˆ‚x + prep_p::PP # prepared gradient for โˆ‚H/โˆ‚p + prep_v::PV # prepared gradient for โˆ‚H/โˆ‚v (nothing if Fixed) +end +``` + +Implement `prepare_cache` using `DI.prepare_gradient` with `Constant` contexts: + +```julia +function Differentiation.prepare_cache( + backend::Differentiation.DifferentiationInterface, + h::Data.AbstractHamiltonian, + typical_x, typical_p, typical_v +) + di_backend = CTSolvers.Strategies.options(backend)[:backend] # e.g. AutoForwardDiff() + typical_t = zero(eltype(typical_x)) + prep_x = DI.prepare_gradient(h_x, di_backend, typical_x, + Constant(typical_p), Constant(typical_v), Constant(typical_t)) + prep_p = DI.prepare_gradient(h_p, di_backend, typical_p, + Constant(typical_x), Constant(typical_v), Constant(typical_t)) + prep_v = typical_v !== nothing ? + DI.prepare_gradient(h_v, di_backend, typical_v, + Constant(typical_x), Constant(typical_p), Constant(typical_t)) : nothing + return DifferentiationInterfaceCache(prep_x, prep_p, prep_v) +end +``` + +Implement `hamiltonian_gradient` and `variable_gradient` using the prepared plans when +a cache is available, falling back to plain `DI.gradient` otherwise. + +> **Note โ€” `build_hamiltonian_vector_field` not included.** Converting a `Hamiltonian` +> to a `HamiltonianVectorField` via AD is a natural future utility but is not on the +> critical path of this plan. It can be added later in `Differentiation` as a public +> utility if a concrete use case arises (e.g. inspection, interoperability). + +### Step 19 โ€” `Project.toml` (modified) + +`ADTypes.jl` is added as a **hard dependency** (`[deps]`) โ€” it is ultra-lightweight +(type definitions only, no computation) and ensures `AutoForwardDiff` is always +available in core without any extension. + +`DifferentiationInterface.jl` is added as a **weak dependency** (`[weakdeps]`) to +trigger the `CTFlowsDifferentiationInterface` extension (gradient computation). + +```toml +[deps] +# add: +ADTypes = "<uuid>" + +[weakdeps] +# add: +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" + +[extensions] +# add: +CTFlowsDifferentiationInterface = ["DifferentiationInterface"] + +[compat] +# add: +ADTypes = "1" +DifferentiationInterface = "1" + +[extras] +# add: +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" + +[targets] +# add to test list: +"DifferentiationInterface" +``` + +`CTFlowsForwardDiff.jl` is **not modified** in this feature. + +### Step 20 โ€” Test Checkpoint: Extension + +- `@testset "Unit: DifferentiationInterfaceCache construction"` +- `@testset "Integration: prepare_cache with AutoForwardDiff()"` โ€” correct preps +- `@testset "Integration: hamiltonian_gradient without cache"` โ€” numerical verification +- `@testset "Integration: hamiltonian_gradient with cache"` โ€” same result, optimised path +- `@testset "Integration: variable_gradient"` โ€” numerical verification +- `@testset "Integration: default backend via CTFlowsForwardDiff"` + +--- + +## Phase 6 โ€” Cache Preparation in `Flows` + +### Step 21 โ€” `src/Flows/calling.jl` (modified) + +Add `prepare_cache` using a three-layer trait-dispatch pattern: + +```julia +# Front-end: extract trait, delegate +function prepare_cache( + sys::Systems.AbstractHamiltonianSystem, + config::Common.AbstractConfig; variable +) + return prepare_cache(Common.ad_trait(sys), sys, config; variable=variable) +end + +# WithoutAD: no preparation needed +function prepare_cache( + ::Type{Common.WithoutAD}, + sys::Systems.AbstractHamiltonianSystem, + config::Common.AbstractConfig; variable +) + return nothing +end + +# WithAD: extract x0, p0 via getters and delegate to the backend +function prepare_cache( + ::Type{Common.WithAD}, + sys::Systems.HamiltonianSystem, + config::Common.AbstractConfig; variable +) + x0 = Common.initial_state(config) + p0 = Common.initial_costate(config) + return Differentiation.prepare_cache(sys.backend, sys.h, x0, p0, variable) +end +``` + +`config` is typed as `AbstractConfig` (not restricted to `AbstractHamiltonianConfig`) so that +the same `prepare_cache` works when `call` is invoked with an `AugmentedHamiltonianPointConfig` +(which defines `initial_state` and `initial_costate` via its own getters โ€” see Step 24a). + +Unified `call` for all `HamiltonianFlow`s โ€” a single implementation, no duplication: + +```julia +function call(flow::Flows.HamiltonianFlow, config::Common.AbstractConfig; variable, unsafe) + sys = system(flow) + int = integrator(flow) + cache = prepare_cache(sys, config; variable=variable) + prob = Integrators.build_problem(int, sys, config; variable=variable, cache=cache) + opts = Integrators.build_options(int, config) + result = Integrators.solve_problem(int, prob, opts; unsafe=unsafe) + return Solutions.build_solution(result, config) +end +``` + +### Step 22 โ€” `ext/CTFlowsSciML/build_and_solve.jl` (modified) + +The existing `build_problem` overload gains a `cache=nothing` keyword: + +```julia +function Integrators.build_problem(integ::SciML, sys, config; variable, cache=nothing) + u0 = Common.initial_condition(config) + p = Common.ODEParameters(variable, cache) + if ismutable(u0) + f! = Systems.rhs(sys) + prob = ODEProblem(f!, u0, Common.tspan(config), p) + else + f = Systems.rhs_oop(sys, false) + prob = ODEProblem(f, u0, Common.tspan(config), p) + end + return prob +end +``` + +A new overload dispatching on `AugmentedHamiltonianPointConfig` builds the augmented RHS +**lazily** using concrete dimensions from the config, which solves the split ambiguity when +`N = nothing`: + +```julia +function Integrators.build_problem( + integ::SciML, + sys::Systems.HamiltonianSystem, + config::Common.AugmentedHamiltonianPointConfig; + variable, cache=nothing +) + u0 = Common.initial_condition(config) # vcat(x0, p0, pv0) + p = Common.ODEParameters(variable, cache) + n_x = length(Common.initial_state(config)) # concrete at call time + n_v = length(Common.initial_variable_costate(config)) + f! = Systems.build_rhs_augmented(sys, n_x, n_v) # lazy โ€” closes over n_x, n_v + return ODEProblem(f!, u0, Common.tspan(config), p) +end +``` + +Corresponding stub in `src/Integrators/abstract_integrator.jl` (throws `NotImplemented`). + +### Step 23 โ€” Test Checkpoint: Cache in call pipeline + +- `@testset "Integration: HamiltonianVectorFieldSystem (WithoutAD) โ€” cache is nothing"` +- `@testset "Integration: HamiltonianSystem (WithAD) โ€” cache prepared and used"` +- `@testset "Integration: p.cache accessible in RHS during solve"` +- `@testset "Regression: existing HamiltonianVectorFieldSystem path unchanged"` + +--- + +## Phase 7 โ€” `augment=true` Support + +### Step 24a โ€” `src/Common/configs.jl` (modified) + +Add a dedicated config type for augmented Hamiltonian integration. Using a proper config +avoids any `augmented=true` boolean on `build_problem` and lets the entire existing +`call` pipeline work unchanged via dispatch. + +**New config struct:** + +```julia +struct AugmentedHamiltonianPointConfig{T0<:Real, X0, P0, PV0, TF<:Real} <: + AbstractConfigWithMaC{X0, PointTrait, AugmentedHamiltonianTrait} + t0::T0 + x0::X0 + p0::P0 + pv0::PV0 # initial variable costate (zeros at start) + tf::TF +end +``` + +`tspan` is inherited from `AbstractPointConfig` (`(c.t0, c.tf)`). + +**Getters** (add to `configs.jl`): + +```julia +function initial_condition(c::AugmentedHamiltonianPointConfig) + return vcat(c.x0, c.p0, c.pv0) # feeds build_problem directly +end + +function initial_state(c::AugmentedHamiltonianPointConfig) + return c.x0 +end + +function initial_costate(c::AugmentedHamiltonianPointConfig) + return c.p0 # enables prepare_cache (WithAD) to work unchanged +end + +function initial_variable_costate(c::AugmentedHamiltonianPointConfig) + return c.pv0 +end +``` + +Export: `AugmentedHamiltonianPointConfig`, `initial_variable_costate`. + +### Step 24 โ€” `src/Flows/calling.jl` (modified) + +Add `augment` keyword to the `HamiltonianFlow` callable: + +```julia +function (f::HamiltonianFlow)( + t0::Real, x0, p0, tf::Real; + variable = Common.__variable(), + unsafe = Common.__unsafe(), + augment::Bool = false, +) + config = Common.HamiltonianPointConfig(t0, x0, p0, tf) + augment && return call_augmented(f, config; variable=variable, unsafe=unsafe) + return call(f, config; variable=variable, unsafe=unsafe) +end +``` + +Implement `call_augmented` using trait dispatch โ€” a front-end extracts both +`ad_trait` and `variable_dependence`, then delegates: + +```julia +# Front-end: extract both traits, delegate +function call_augmented( + flow::HamiltonianFlow, + config::Common.HamiltonianPointConfig; variable, unsafe +) + sys = system(flow) + return call_augmented( + Common.ad_trait(sys), + Common.variable_dependence(sys), + flow, config; variable=variable, unsafe=unsafe + ) +end + +# WithoutAD โ€” any VD: clear message +function call_augmented( + ::Type{Common.WithoutAD}, ::Type{<:Common.VariableDependence}, + flow::HamiltonianFlow, config::Common.HamiltonianPointConfig; variable, unsafe +) + throw(IncorrectArgument( + "augment=true is not supported on this flow"; + reason = "The flow was built from a HamiltonianVectorField, not a scalar Hamiltonian", + suggestion = "Use Flow(h::Hamiltonian; ...) with is_variable=true to enable augmented integration", + context = "HamiltonianFlow call with augment=true", + )) +end + +# Any AT โ€” Fixed: clear message +function call_augmented( + ::Type{<:Common.WithAD}, ::Type{Common.Fixed}, + flow::HamiltonianFlow, config::Common.HamiltonianPointConfig; variable, unsafe +) + throw(IncorrectArgument( + "augment=true requires a variable-dependent Hamiltonian"; + reason = "The system has Fixed variable dependence โ€” there is no v to differentiate against", + suggestion = "Define your Hamiltonian with is_variable=true and pass variable= at call time", + context = "HamiltonianFlow call with augment=true", + )) +end + +# WithAD + NonFixed: valid path +function call_augmented( + ::Type{Common.WithAD}, ::Type{Common.NonFixed}, + flow::HamiltonianFlow, config::Common.HamiltonianPointConfig; variable, unsafe +) + sys = system(flow) + x0 = Common.initial_state(config) + pv0 = zeros(eltype(x0), length(variable)) + config_aug = Common.AugmentedHamiltonianPointConfig( + config.t0, x0, Common.initial_costate(config), pv0, config.tf + ) + return call(flow, config_aug; variable=variable, unsafe=unsafe) +end +``` + +**Key simplification**: `call(flow, config_aug; ...)` reuses the existing pipeline unchanged: + +- `prepare_cache` uses `initial_state`/`initial_costate` on `AugmentedHamiltonianPointConfig` โœ“ +- `build_problem` dispatches on `AugmentedHamiltonianPointConfig` โ†’ calls `build_rhs_augmented(sys, n_x, n_v)` โœ“ +- `build_solution` dispatches on `AugmentedHamiltonianTrait` โ†’ returns `(xf, pf, pvf)` โœ“ + +### Step 25 โ€” `src/Solutions/building.jl` (modified) + +Add an internal split helper and a `build_solution` overload dispatching on +`AugmentedHamiltonianTrait`. Correct splitting uses `length(x0)` and `length(pv0)` +from the config getters โ€” no dimension arithmetic that would break when `n_x โ‰  n_v`. + +**Split helper** (parallel to `_ham_split_solution`): + +```julia +_aug_split_solution(u, x0::AbstractVector, pv0::AbstractVector) = ( + u[1:length(x0)], + u[length(x0)+1:end-length(pv0)], + u[end-length(pv0)+1:end], +) +``` + +**New `build_solution` overload** (dispatches via `AugmentedHamiltonianTrait`): + +```julia +function build_solution( + result::Integrators.AbstractIntegrationResult, + sys::Systems.AbstractHamiltonianSystem, + config::Common.AbstractPointConfig{X0, Common.AugmentedHamiltonianTrait} +) where {X0} + u = Integrators.final_state(result) + return _aug_split_solution( + u, + Common.initial_state(config), + Common.initial_variable_costate(config), + ) +end +``` + +Returns `(xf, pf, pvf)`. `build_augmented_solution` from the original draft is **dropped**. + +### Step 26 โ€” Test Checkpoint: `augment=true` + +- `@testset "Unit: AugmentedHamiltonianPointConfig construction"` โ€” fields, `initial_condition`, getters +- `@testset "Unit: prepare_cache on AugmentedHamiltonianPointConfig"` โ€” same result as on `HamiltonianPointConfig` +- `@testset "Error: augment=true on WithoutAD flow"` โ€” `IncorrectArgument` with message +- `@testset "Error: augment=true on Fixed system"` โ€” `IncorrectArgument` with message +- `@testset "Integration: augment=true returns (xf, pf, pvf)"` +- `@testset "Integration: pvf = -โˆซ โˆ‚H/โˆ‚v dt"` โ€” numerical verification against finite differences +- `@testset "Regression: augment=false unchanged"` + +--- + +## Phase 8 โ€” High-Level `Flow(h::Hamiltonian; ...)` Constructor + +>[!NOTE] +> Deprecated: this phase is replaced by "Phase 8 (rรฉvisรฉe) โ€” Routage des options vers `:di` et `:sciml` dans `Flow(h::Hamiltonian; ...)`" + +### Step 27 โ€” `src/Flows/building.jl` (modified) + +```julia +function Flow(h::Data.AbstractHamiltonian; ad_backend=nothing, opts...) + backend = _resolve_ad_backend(ad_backend) + sys = Systems.build_system(h, backend) + integ = Integrators.build_integrator(; opts...) + return build_flow(sys, integ) +end + +function Flow(h::Data.AbstractHamiltonian, state_dimension::Int; ad_backend=nothing, opts...) + backend = _resolve_ad_backend(ad_backend) + sys = Systems.build_system(h, state_dimension, backend) + integ = Integrators.build_integrator(; opts...) + return build_flow(sys, integ) +end +``` + +`_resolve_ad_backend` falls back to the default registered by `CTFlowsForwardDiff`: + +```julia +function _resolve_ad_backend(ad_backend) + ad_backend !== nothing && return ad_backend + default = Differentiation.__default_ad_backend() + default === missing && throw(IncorrectArgument( + "No AD backend available"; + suggestion = "Load ForwardDiff (or another supported backend) before calling Flow(h::Hamiltonian).", + )) + return Differentiation.build_ad_backend(; backend=default) +end +``` + +### Step 28 โ€” Test Checkpoint: High-level constructor + +- `@testset "Unit: Flow(H) with explicit backend"` +- `@testset "Unit: Flow(H, n) with dimension"` +- `@testset "Integration: Flow(H) โ†’ call โ€” end-to-end"` +- `@testset "Integration: Flow(H) โ†’ augment=true โ€” end-to-end"` +- `@testset "Integration: default backend when ForwardDiff loaded"` +- `@testset "Error: no backend available"` โ€” clear message + +--- + +## Phase 9 โ€” Documentation + +### Step 29 โ€” Docstrings + +Write docstrings for all new and modified public-facing items: + +- `src/Data/hamiltonian.jl` โ€” `AbstractHamiltonian`, `Hamiltonian`, constructor, call signatures +- `src/Common/abstract_trait.jl` โ€” `AbstractADTrait`, `WithAD`, `WithoutAD`, `AbstractCache` +- `src/Common/ode_parameters.jl` โ€” updated `ODEParameters` with cache field +- `src/Differentiation/abstract_ad_backend.jl` โ€” `AbstractADBackend`, all three stubs +- `src/Differentiation/differentiation_interface.jl` โ€” `DifferentiationInterface` strategy +- `src/Differentiation/building.jl` โ€” `build_ad_backend` +- `src/Systems/hamiltonian_system.jl` โ€” `HamiltonianSystem`, constructors, accessors +- `src/Systems/building.jl` โ€” new `build_system` overloads +- `src/Common/configs.jl` โ€” `AugmentedHamiltonianPointConfig`, `initial_variable_costate` +- `src/Flows/calling.jl` โ€” `prepare_cache`, `call_augmented`, `augment` parameter +- `src/Flows/building.jl` โ€” `Flow(h::AbstractHamiltonian; ...)` +- `src/Solutions/building.jl` โ€” `_aug_split_solution`, `build_solution` augmented overload +- `ext/CTFlowsDifferentiationInterface.jl` โ€” `DifferentiationInterfaceCache`, implementations + +### Step 30 โ€” Final Test Run + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/hamiltonian_feature.log +grep -E "Error|Fail|Test Summary" /tmp/hamiltonian_feature.log +``` + +Expected: all suites pass, zero failures, zero errors. + +--- + +# Phase 8 (rรฉvisรฉe) โ€” Routage des options vers `:di` et `:sciml` dans `Flow(h::Hamiltonian; ...)` + +## Vue d'ensemble + +L'objectif est de remplacer `ad_backend=nothing` par un mรฉcanisme flat-kwargs identique ร  ce que fait `solve_descriptive` dans OptimalControl.jl. La description complรจte est fixe : `(:di, :sciml)`. Il n'y a pas d'options d'action โ€” seules existent les options de stratรฉgie. + +La chaรฎne d'appel sera : + +``` +Flow(h; kwargs...) + โ””โ”€ _flow_families() # (backend, integrator) โ†’ types abstraits + โ””โ”€ _route_flow_options(kwargs) # route_all_options avec action_defs=[] + โ””โ”€ _build_flow_components(h, routed) # build_strategy_from_resolved ร—2 + โ””โ”€ build_flow(sys, integ) # inchangรฉ +``` + +--- + +## Step 27a โ€” Registre CTFlows `src/Flows/registry.jl` (nouveau fichier) + +CTFlows a besoin d'un `StrategyRegistry` propre ร  son pรฉrimรจtre, exactement comme OptimalControl.jl. Ce fichier est le **seul endroit** oรน les deux familles sont inscrites ensemble. + +```julia +# src/Flows/registry.jl + +""" +Default strategy registry for CTFlows. +Maps AbstractADBackend and AbstractIntegrator to their concrete implementations. +""" +const _FLOW_REGISTRY = CTSolvers.Strategies.create_registry( + Differentiation.AbstractADBackend => (Differentiation.DifferentiationInterface,), + Integrators.AbstractIntegrator => (Integrators.SciML,), +) + +function flow_registry() + return _FLOW_REGISTRY +end +``` + +Note : le registre est dรฉclarรฉ comme constante de module (pattern identique ร  OptimalControl.jl). Il est exposรฉ via `flow_registry()` pour faciliter les tests et une รฉventuelle extension future (registre injectรฉ). + +--- + +## Step 27b โ€” Helpers de routage `src/Flows/flow_routing.jl` (nouveau fichier) + +Ce fichier est l'analogue direct de `descriptive_routing.jl` dans OptimalControl.jl, simplifiรฉ car il n'y a aucune option d'action. + +```julia +# src/Flows/flow_routing.jl + +# --------------------------------------------------------------------------- +# F1 โ€” Familles +# --------------------------------------------------------------------------- + +""" +Return the strategy families for Flow option routing. + :backend โ†’ AbstractADBackend (strategy id: :di) + :integrator โ†’ AbstractIntegrator (strategy id: :sciml) +""" +function _flow_families() + return ( + backend = Differentiation.AbstractADBackend, + integrator = Integrators.AbstractIntegrator, + ) +end + +# La description complรจte est fixe pour Flow(h::Hamiltonian; ...) +const _FLOW_DESCRIPTION = (:di, :sciml) + +# --------------------------------------------------------------------------- +# F2 โ€” Routage des options +# --------------------------------------------------------------------------- + +""" +Route all kwargs to :backend or :integrator families. +No action options โ€” action_defs is empty. +""" +function _route_flow_options(kwargs) + return CTSolvers.Orchestration.route_all_options( + _FLOW_DESCRIPTION, + _flow_families(), + CTSolvers.Options.OptionDefinition[], # pas d'options d'action + (; kwargs...), + flow_registry(); + source_mode = :description, + ) +end + +# --------------------------------------------------------------------------- +# F3 โ€” Construction des composants +# --------------------------------------------------------------------------- + +""" +Build backend + integrator from routed options. +Returns (backend::AbstractADBackend, integrator::AbstractIntegrator). +""" +function _build_flow_components(routed) + families = _flow_families() + resolved = CTSolvers.Orchestration.resolve_method( + _FLOW_DESCRIPTION, families, flow_registry() + ) + backend = CTSolvers.Orchestration.build_strategy_from_resolved( + resolved, :backend, families, flow_registry(); + routed.strategies.backend... + ) + integrator = CTSolvers.Orchestration.build_strategy_from_resolved( + resolved, :integrator, families, flow_registry(); + routed.strategies.integrator... + ) + return (backend = backend, integrator = integrator) +end +``` + +Points importants : + +- `action_defs = []` โ€” aucune option d'action, tout passe dans les stratรฉgies. +- `source_mode = :description` โ€” messages d'erreur orientรฉs utilisateur. +- `_FLOW_DESCRIPTION` est une constante : la description est toujours `(:di, :sciml)`, il n'y a pas ร  la complรฉter dynamiquement. +- On รฉvite un double appel ร  `resolve_method` en factorisant dans `_build_flow_components`. + +--- + +## Step 27c โ€” `src/Flows/building.jl` (modifiรฉ) + +Le constructeur `Flow(h::Hamiltonian; ...)` devient : + +```julia +function Flow(h::Data.AbstractHamiltonian; kwargs...) + routed = _route_flow_options(kwargs) + components = _build_flow_components(routed) + sys = Systems.build_system(h, components.backend) + return build_flow(sys, components.integrator) +end + +function Flow(h::Data.AbstractHamiltonian, state_dimension::Int; kwargs...) + routed = _route_flow_options(kwargs) + components = _build_flow_components(routed) + sys = Systems.build_system(h, state_dimension, components.backend) + return build_flow(sys, components.integrator) +end +``` + +Il n'y a plus de `_resolve_ad_backend` ni de `ad_backend=nothing`. Le backend par dรฉfaut est dรฉclarรฉ dans `Differentiation.DifferentiationInterface` via son `OptionDefinition` (`:backend` โ†’ `AutoForwardDiff()` par dรฉfaut). Si `DifferentiationInterface.jl` n'est pas chargรฉ, le stub `hamiltonian_gradient` lancera `NotImplemented` au moment de l'appel, pas ร  la construction โ€” ce comportement est cohรฉrent avec le pattern extension/weakdep. + +--- + +## Step 27d โ€” `src/Flows/Flows.jl` (modifiรฉ) + +Ajouter les deux nouveaux includes (dans l'ordre, aprรจs les imports de `Differentiation` et `Integrators`) : + +```julia +include("registry.jl") +include("flow_routing.jl") +``` + +--- + +## Step 28 โ€” Test Checkpoint : routage des options + +Fichier : `test/suite/flows/test_flow_routing.jl` + +| Testset | Ce qui est vรฉrifiรฉ | +|---|---| +| `"Unit: _flow_families"` | Renvoie un `NamedTuple` avec les deux clรฉs et les bons types abstraits | +| `"Unit: _FLOW_DESCRIPTION"` | Vaut `(:di, :sciml)` | +| `"Unit: flow_registry"` | Les deux familles sont inscrites avec les bons types concrets | +| `"Unit: _route_flow_options โ€” empty kwargs"` | `routed.strategies.backend` et `.integrator` vides, pas d'erreur | +| `"Unit: _route_flow_options โ€” integrator option"` | Ex. `abstol=1e-10` routรฉ vers `:integrator` | +| `"Unit: _route_flow_options โ€” backend option"` | Ex. `backend=AutoForwardDiff()` routรฉ vers `:backend` | +| `"Unit: _route_flow_options โ€” unknown option"` | `IncorrectArgument` avec suggestion | +| `"Unit: _route_flow_options โ€” disambiguation"` | `route_to(di=..., sciml=...)` acceptรฉ si option ambiguรซ | +| `"Unit: _build_flow_components โ€” defaults"` | Renvoie `DifferentiationInterface` + `SciML` avec options par dรฉfaut | +| `"Unit: _build_flow_components โ€” with options"` | Les options sont bien passรฉes aux constructeurs | +| `"Integration: Flow(h) โ€” end-to-end"` | Flot crรฉรฉ, callable, rรฉsultat correct | +| `"Integration: Flow(h, n) โ€” end-to-end"` | Idem avec `state_dimension` | +| `"Integration: Flow(h; reltol=1e-9)"` | Option SciML passรฉe, vรฉrifiable via `options(integrator(sys))` | +| `"Regression: no ad_backend kwarg needed"` | `Flow(h)` fonctionne sans aucun kwarg | + +--- + +## Fichiers impactรฉs (delta vs plan initial) + +| Fichier | Statut | Changement | +|---|---|---| +| `src/Flows/registry.jl` | **nouveau** | Registre CTFlows | +| `src/Flows/flow_routing.jl` | **nouveau** | `_flow_families`, `_route_flow_options`, `_build_flow_components` | +| `src/Flows/building.jl` | **modifiรฉ** | Supprime `_resolve_ad_backend`/`ad_backend=nothing`, dรฉlรจgue au routeur | +| `src/Flows/Flows.jl` | **modifiรฉ** | Ajoute les deux `include` | +| `test/suite/flows/test_flow_from_hamiltonian.jl` | **modifiรฉ** | Remplace les testsets Step 28 du plan initial par ceux ci-dessus | + +`_resolve_ad_backend` et `Differentiation.__default_ad_backend()` du plan initial sont **supprimรฉs** โ€” la valeur par dรฉfaut est portรฉe par le `OptionDefinition` de `DifferentiationInterface`, pas par une variable globale dans `Differentiation`. + +--- + +## Note sur les dรฉpendances + +`CTSolvers.Orchestration` doit รชtre importรฉ dans `Flows.jl` (il l'est peut-รชtre dรฉjร  via `Integrators` qui utilise `CTSolvers.Strategies`). Si ce n'est pas le cas, ajouter `using CTSolvers: CTSolvers` dans `Flows.jl` et qualifier `CTSolvers.Orchestration.route_all_options` explicitement, exactement comme le fait OptimalControl.jl. + +## Ressources + +### Documentation CTSolvers + +- <https://control-toolbox.org/CTSolvers.jl/stable/guides/orchestration_and_routing.html> +- <https://control-toolbox.org/CTSolvers.jl/stable/api/orchestration_public.html#CTSolvers.Orchestration.route_all_options> +- <https://control-toolbox.org/CTSolvers.jl/stable/api/orchestration_public.html#resolve_method> +- <https://control-toolbox.org/CTSolvers.jl/stable/api/orchestration_public.html#build_strategy_from_resolved> + +### Code source OptimalControl.jl (rรฉfรฉrence d'implรฉmentation) + +- <https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/solve/descriptive.jl> +- <https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/helpers/descriptive_routing.jl> + +### Dรฉpรดt cible + +- <https://github.com/control-toolbox/CTFlows.jl/tree/develop> \ No newline at end of file diff --git a/reports/hamilonian/save/hamiltonian_plan_old.md b/reports/hamilonian/save/hamiltonian_plan_old.md new file mode 100644 index 00000000..86638060 --- /dev/null +++ b/reports/hamilonian/save/hamiltonian_plan_old.md @@ -0,0 +1,378 @@ +# Plan d'implรฉmentation โ€” type `Hamiltonian` dans CTFlows.jl + +## Vue d'ensemble + +L'ajout du type `Hamiltonian` suit la chaรฎne de construction suivante : + +``` +Hamiltonian + ADStrategy + โ”‚ + โ–ผ +HamiltonianVectorField โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ (chemin direct alternatif) + โ–ผ โ”‚ +HamiltonianSystem โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + (+ rhs augmentรฉ si NonFixed) + โ”‚ + โ–ผ +HamiltonianFlow โ”€โ”€โ†’ call(augment=true) โ”€โ”€โ†’ solution augmentรฉe +``` + +--- + +## Phase 1 โ€” Type `Hamiltonian` dans le module `Data` + +### 1.1 Dรฉfinition du type + +Nouveau fichier : `src/data/hamiltonian.jl` + +```julia +struct Hamiltonian{F<:Function, TD<:TimeDependence, VD<:VariableDependence, MD<:AbstractMutabilityTrait} + f::F +end +``` + +Traits identiques ร  `VectorField` et `HamiltonianVectorField`. La fonction encapsulรฉe retourne un **scalaire** : `H([t,] x, p[, v]) -> Real`. + +### 1.2 Dรฉtection de mutabilitรฉ + +Helpers internes `_oop_arity_h` et `_detect_mutability_h` (mรชme pattern que `_oop_arity_vf` / `_detect_mutability_vf`) : + +| TD | VD | aritรฉ OOP | +|---|---|---| +| Autonomous | Fixed | 2 (x, p) | +| NonAutonomous | Fixed | 3 (t, x, p) | +| Autonomous | NonFixed | 3 (x, p, v) | +| NonAutonomous | NonFixed | 4 (t, x, p, v) | + +Pour `InPlace`, aritรฉ = OOP + 1 (un buffer de sortie scalaire : `dH`). +> **Note :** le cas InPlace pour un hamiltonien scalaire est rare, mais on le supporte par cohรฉrence avec le reste du package. + +### 1.3 Constructeur avec keyword args + +```julia +function Hamiltonian(f; + is_autonomous::Bool = Common.__is_autonomous(), + is_variable::Bool = Common.__is_variable(), + is_inplace::Union{Bool, Nothing} = Common.__is_inplace() +) +``` + +### 1.4 Signatures d'appel + +- **Naturelles** : une par combinaison de traits (8 au total, comme `VectorField`). +- **Uniformes** `(t, x, p, v)` : utilisรฉes en interne par les helpers AD pour ne pas se soucier des traits. + +### 1.5 `Base.show` + +Format identique aux autres types du module `Data`. + +### 1.6 Export + +Ajouter `Hamiltonian` aux exports de `Data` et ร  `Systems.jl`. + +--- + +## Phase 2 โ€” Stratรฉgie AD dans le module `Integrators` + +### Motivation + +Pour construire un `HamiltonianVectorField` ร  partir d'un `Hamiltonian`, il faut un backend de diffรฉrentiation automatique (`โˆ‚H/โˆ‚x`, `โˆ‚H/โˆ‚p`, `โˆ‚H/โˆ‚v`). Ce backend est une **stratรฉgie au sens de `CTSolvers`**, ce qui permet : + +- de router les options ร  plat lors de `Flow(h; ad_backend=:forwarddiff, reltol=1e-8, ...)` ; +- d'avoir des messages d'erreur structurรฉs si les options sont invalides ; +- d'รชtre extensible sans modifier le code existant (OCP). + +### 2.1 Hiรฉrarchie de types + +Nouveau fichier : `src/integrators/ad_strategy.jl` + +```julia +abstract type AbstractADStrategy <: CTSolvers.Strategies.AbstractStrategy end + +struct ForwardDiffADTag <: Common.AbstractTag end + +struct ForwardDiffAD{O<:CTSolvers.Strategies.StrategyOptions} <: AbstractADStrategy + options::O +end +``` + +### 2.2 Contrat `CTSolvers.Strategies` + +```julia +CTSolvers.Strategies.id(::Type{<:ForwardDiffAD}) = :forwarddiff_ad +CTSolvers.Strategies.description(::Type{<:ForwardDiffAD}) = "ForwardDiff AD backend for Hamiltonian gradient." +CTSolvers.Strategies.metadata(::Type{<:ForwardDiffAD}) = ... # options : chunk_size, tag... +``` + +### 2.3 Stub + extension + +Mรชme pattern que `SciML` : +- `build_ad_strategy(::Type{<:AbstractTag}; kwargs...)` lรจve `ExtensionError` par dรฉfaut. +- L'extension `CTFlowsForwardDiff` (chargรฉe par `using ForwardDiff`) fournit l'implรฉmentation rรฉelle. + +### 2.4 Fonction `gradient_hamiltonian` + +```julia +# Interface publique de la stratรฉgie AD +function gradient_hamiltonian(ad::AbstractADStrategy, h::Data.Hamiltonian, t, x, p, v) + # Retourne (dx, dp) = (โˆ‚H/โˆ‚p, -โˆ‚H/โˆ‚x) via la signature uniforme de h +end + +function gradient_hamiltonian_variable(ad::AbstractADStrategy, h::Data.Hamiltonian, t, x, p, v) + # Retourne dpv = -โˆ‚H/โˆ‚v +end +``` + +Ces fonctions sont implรฉmentรฉes dans l'extension et utilisent la signature uniforme `h(t, x, p, v)` pour faire abstraction des traits. + +--- + +## Phase 3 โ€” Conversion `Hamiltonian` โ†’ `HamiltonianVectorField` + +### 3.1 Nouvelle fonction dans `Data` + +```julia +function hamiltonian_vector_field(h::Hamiltonian{F, TD, VD, OutOfPlace}, ad) -> HamiltonianVectorField +``` + +Construit un `HamiltonianVectorField{..., OutOfPlace}` dont la fonction interne est : + +```julia +(t, x, p, v) -> gradient_hamiltonian(ad, h, t, x, p, v) +``` + +Les traits `TD`, `VD` sont hรฉritรฉs du `Hamiltonian`. Le rรฉsultat est toujours **OutOfPlace** (le gradient AD retourne une nouvelle valeur). + +> **Principe KISS :** on ne construit pas une version InPlace de ce vecteur champ issu de l'AD ; si l'utilisateur veut InPlace, il รฉcrit directement son `HamiltonianVectorField`. + +### 3.2 Localisation + +Ce code vit dans `src/data/hamiltonian.jl` ou un fichier `src/data/hamiltonian_conversions.jl`, et dรฉpend de l'interface `gradient_hamiltonian` (abstraction, pas de l'implรฉmentation concrรจte). + +--- + +## Phase 4 โ€” `HamiltonianSystem` depuis un `Hamiltonian` + +### 4.1 Nouveaux constructeurs dans `build_system` + +```julia +# Sans dimension connue +function build_system(h::Data.Hamiltonian, ad::Integrators.AbstractADStrategy) + hvf = Data.hamiltonian_vector_field(h, ad) + return HamiltonianSystem(hvf) +end + +# Avec dimension connue +function build_system(h::Data.Hamiltonian, state_dimension::Int, ad::Integrators.AbstractADStrategy) + hvf = Data.hamiltonian_vector_field(h, ad) + return HamiltonianSystem(hvf, state_dimension) +end +``` + +### 4.2 Stockage du `Hamiltonian` original dans `HamiltonianSystem` (pour l'augmentation) + +Le `HamiltonianSystem` doit optionnellement conserver le `Hamiltonian` source ET le `AbstractADStrategy`, afin de construire le RHS augmentรฉ. Deux options : + +**Option A โ€” Stocker dans le type (recommandรฉ)** + +Ajouter des paramรจtres de type optionnels ร  `HamiltonianSystem` : + +```julia +struct HamiltonianSystem{N, F, TD, VD, MD, RHS, OOPROHS, FINRHS, AUGMENTRHS} <: ... + ... + rhs_augmented::AUGMENTRHS # Nothing si pas de Hamiltonian source +end +``` + +`AUGMENTRHS = Nothing` pour les systรจmes construits depuis un `HamiltonianVectorField` direct. +`AUGMENTRHS = <closure>` pour les systรจmes construits depuis un `Hamiltonian` + AD. + +**Option B โ€” Laisser au `HamiltonianFlow`** + +Le flow stocke sรฉparรฉment `(system, integrator, hamiltonian_source, ad_strategy)` et construit le RHS augmentรฉ ร  la demande lors de `call(augment=true)`. + +> **Recommandation : Option A**, car elle suit le principe de DIP (le systรจme encapsule tout ce dont il a besoin) et รฉvite d'alourdir le `Flow`. + +--- + +## Phase 5 โ€” RHS augmentรฉ + +### 5.1 Principe + +Pour un `Hamiltonian` `NonFixed` et `augment=true`, on intรจgre **sans** ajouter `v` comme รฉtat (contrairement ร  l'ancienne implรฉmentation) : + +- ร‰tat augmentรฉ : `z_aug = [x; p; pv]` de taille `2n + m` +- `v` est transmis comme paramรจtre via `ODEParameters` (dรฉjร  existant) +- Dynamique : + - `dx/dt = โˆ‚H/โˆ‚p` + - `dp/dt = -โˆ‚H/โˆ‚x` + - `dpv/dt = -โˆ‚H/โˆ‚v` + +### 5.2 Construction + +Nouveau helper interne dans `src/systems/hamiltonian_system.jl` : + +```julia +function _build_rhs_augmented(h::Data.Hamiltonian, ad::AbstractADStrategy, ::Val{N}, ::Val{M}) + return function (du, u, ฮป, t) + x, p, pv = _aug_split(u, N, M) + dx_du, dp_du = gradient_hamiltonian(ad, h, t, x, p, ฮป.variable) + dpv_du = gradient_hamiltonian_variable(ad, h, t, x, p, ฮป.variable) + _aug_assign!(du, dx_du, dp_du, dpv_du, N, M) + return nothing + end +end +``` + +Helpers `_aug_split` / `_aug_assign!` analogues ร  `_ham_split` / `_ham_assign!`. + +> **Note :** `N` et `M` peuvent รชtre `nothing` (infรฉrence runtime). Si `N=nothing`, l'infรฉrence de `M` se fait sur `length(ฮป.variable)`. + +### 5.3 Quand construire ce RHS ? + +ร€ la construction du `HamiltonianSystem` depuis un `Hamiltonian`. Si le systรจme est `Fixed` (pas de variable), `rhs_augmented = nothing`. Si `NonFixed`, le RHS augmentรฉ est prรฉ-calculรฉ et stockรฉ. + +--- + +## Phase 6 โ€” `HamiltonianFlow` et option `augment` + +### 6.1 Appel du flow + +Le flow reรงoit `augment=false` par dรฉfaut. Lorsque `augment=true` : + +- Le flow doit รชtre `NonFixed` (sinon `IncorrectArgument`). +- Le `HamiltonianSystem` doit avoir `rhs_augmented โ‰  nothing` (sinon `PreconditionError` : "ce systรจme n'a pas รฉtรฉ construit depuis un Hamiltonian"). + +```julia +function (f::HamiltonianFlow)(t0, x0, p0, tf; + variable = Common.__variable(), + unsafe = Common.__unsafe(), + augment = false, +) + config = augment ? + Common.HamiltonianAugmentedPointConfig(t0, x0, p0, tf) : + Common.HamiltonianPointConfig(t0, x0, p0, tf) + return call(f, config; variable=variable, unsafe=unsafe) +end +``` + +### 6.2 Nouveau `Config` : `HamiltonianAugmentedPointConfig` / `HamiltonianAugmentedTrajectoryConfig` + +Ces configs signalent au `call` d'utiliser `rhs_augmented` et de dรฉcouper la solution en `(xf, pf, pvf)`. + +### 6.3 Adaptation de `call` + +```julia +function call(flow::HamiltonianFlow, config::HamiltonianAugmentedPointConfig; variable, unsafe) + sys = system(flow) + int = integrator(flow) + # Vรฉrification + sys.rhs_augmented === nothing && throw(PreconditionError(...)) + # Construire u0 augmentรฉ : [x0; p0; zeros(m)] + u0_aug = _build_u0_augmented(config, variable) + prob = Integrators.build_problem(int, sys.rhs_augmented, u0_aug, config; variable=variable) + opts = Integrators.build_options(int, config) + result = Integrators.solve_problem(int, prob, opts; unsafe=unsafe) + return Solutions.build_augmented_solution(result, sys, config) +end +``` + +### 6.4 Solution augmentรฉe + +`build_augmented_solution` dรฉcoupe `u_final = [xf; pf; pvf]` et retourne soit : +- Un triplet `(xf, pf, pvf)` pour le mode point, +- Des trajectoires `(t -> x(t), t -> p(t), t -> pv(t))` pour le mode trajectoire. + +--- + +## Phase 7 โ€” Constructeurs haut niveau `Flow` depuis un `Hamiltonian` + +Nouveau fichier ou ajout dans `src/flows/building.jl` : + +```julia +function Flow(h::Data.Hamiltonian; ad=nothing, opts...) + ad_strategy = Integrators.build_ad_strategy(; backend=ad) + system = Systems.build_system(h, ad_strategy) + integrator = Integrators.build_integrator(; opts...) + return build_flow(system, integrator) +end + +function Flow(h::Data.Hamiltonian, state_dimension::Int; ad=nothing, opts...) + ad_strategy = Integrators.build_ad_strategy(; backend=ad) + system = Systems.build_system(h, state_dimension, ad_strategy) + integrator = Integrators.build_integrator(; opts...) + return build_flow(system, integrator) +end +``` + +L'option `ad` (ou `ad_backend`) est routรฉe vers la stratรฉgie AD via `CTSolvers`. + +--- + +## Phase 8 โ€” Tests + +### 8.1 `Data.Hamiltonian` + +- Construction avec toutes les combinaisons de traits. +- Auto-dรฉtection de mutabilitรฉ (OOP / IP). +- Signatures d'appel naturelles et uniformes. +- `Base.show`. +- Erreurs : aritรฉ invalide, mรฉthodes multiples. + +### 8.2 Stratรฉgie AD + +- `ForwardDiffAD` : construction, `id`, `description`, `metadata`. +- `gradient_hamiltonian` sur tous les traits. +- `gradient_hamiltonian_variable` (NonFixed uniquement). +- `ExtensionError` sans l'extension chargรฉe. +- LSP : tester `gradient_hamiltonian` pour tous les sous-types de `AbstractADStrategy` (prรฉparer un mock). + +### 8.3 `HamiltonianSystem` depuis `Hamiltonian` + +- `build_system(h, ad)` et `build_system(h, n, ad)`. +- `rhs_augmented !== nothing` si NonFixed. +- `rhs_augmented === nothing` si Fixed. + +### 8.4 Flow augmentรฉ + +- `flow(t0, x0, p0, tf; augment=true)` pour un hamiltonien NonFixed. +- `PreconditionError` si `augment=true` sur un systรจme construit depuis `HamiltonianVectorField` direct. +- `IncorrectArgument` si `augment=true` sur un flow Fixed. +- Vรฉrification numรฉrique : `pvf = -โˆซ โˆ‚H/โˆ‚v dt` sur un exemple analytique. + +--- + +## Rรฉcapitulatif des fichiers ร  crรฉer / modifier + +| Fichier | Action | +|---|---| +| `src/data/hamiltonian.jl` | **Crรฉer** : type, constructeur, signatures, show | +| `src/data/hamiltonian_conversions.jl` | **Crรฉer** : `hamiltonian_vector_field` | +| `src/data/data.jl` | **Modifier** : include + export `Hamiltonian` | +| `src/integrators/ad_strategy.jl` | **Crรฉer** : `AbstractADStrategy`, `ForwardDiffAD`, stubs | +| `src/integrators/integrators.jl` | **Modifier** : include + export | +| `src/systems/hamiltonian_system.jl` | **Modifier** : nouveau champ `rhs_augmented`, helpers `_aug_*` | +| `src/systems/building.jl` | **Modifier** : surcharges `build_system(h, ad)` | +| `src/common/configs.jl` | **Modifier** : ajouter `HamiltonianAugmented*Config` | +| `src/flows/building.jl` | **Modifier** : `Flow(h::Hamiltonian; ...)` | +| `src/flows/flow.jl` | **Modifier** : `call` pour configs augmentรฉes | +| `src/solutions/...` | **Modifier** : `build_augmented_solution` | +| `ext/CTFlowsForwardDiff/` | **Crรฉer** : extension implรฉmentant `gradient_hamiltonian` | +| `test/data/test_hamiltonian.jl` | **Crรฉer** | +| `test/integrators/test_ad_strategy.jl` | **Crรฉer** | +| `test/flows/test_hamiltonian_flow_augmented.jl` | **Crรฉer** | + +--- + +## Points de vigilance + +1. **Dรฉpendance circulaire** : `gradient_hamiltonian` est dรฉclarรฉe dans `Integrators` (ou un module `AD`) mais utilisรฉe dans `Systems`. Utiliser une interface abstraite dans `Common` ou passer le backend comme argument (DIP). + +2. **Dimension de la variable `v`** : quand `N=nothing`, `M` (dimension de `v`) ne peut pas รชtre dรฉduit statiquement. L'infรฉrence runtime via `length(ฮป.variable)` doit รชtre robuste (et couverte par les tests). + +3. **Routing des options** : l'option `ad` (ou `ad_backend`) ne doit pas รชtre passรฉe ร  la stratรฉgie `SciML`. S'assurer que `CTSolvers` gรจre bien le routage par `id` de stratรฉgie. + +4. **`augment=true` sans variable** : lever une erreur claire (`IncorrectArgument`) avec suggestion de retirer `augment=true`. + +5. **Cohรฉrence `Base.show`** : le `HamiltonianSystem` doit afficher si `rhs_augmented` est disponible. diff --git a/reports/hamilonian/save/plan-save.md b/reports/hamilonian/save/plan-save.md new file mode 100644 index 00000000..f8a82724 --- /dev/null +++ b/reports/hamilonian/save/plan-save.md @@ -0,0 +1,664 @@ +<!-- File: /Users/ocots/.windsurf/plans/hamiltonian-type-feature-28b051 --> +# Hamiltonian Type Feature Implementation Plan + +This plan adds a `Hamiltonian` type that wraps a scalar Hamiltonian function `H([t,], x, p[, v])` with traits (mutability, time dependence, variable dependence), enables automatic construction of `HamiltonianVectorField` via DifferentiationInterface.jl backends, supports manual `HamiltonianVectorField` specification, and provides augmented flow integration to compute variable costates without adding zero dynamics. + +## What Changes and Why + +**New types:** +- `Hamiltonian{F, TD, VD, MD}` in `Data` module - wrapper for scalar Hamiltonian functions +- `HamiltonianAugmentedPointConfig` and `HamiltonianAugmentedTrajectoryConfig` in `Common` - configs for augmented integration + +**Modified types:** +- `HamiltonianSystem` - add optional `rhs_augmented` field and `hamiltonian_source` field to support augmented integration +- `HamiltonianFlow` callable methods - add `augment` keyword argument + +**New functions:** +- `hamiltonian_vector_field(h::Hamiltonian, backend)` - converts Hamiltonian to HamiltonianVectorField via DifferentiationInterface +- `gradient_hamiltonian(backend, h, t, x, p, v)` - computes (โˆ‚H/โˆ‚p, -โˆ‚H/โˆ‚x) using DifferentiationInterface backends +- `gradient_hamiltonian_variable(backend, h, t, x, p, v)` - computes -โˆ‚H/โˆ‚v for augmented integration +- `build_system(h::Hamiltonian, backend)` - builds HamiltonianSystem from Hamiltonian +- `build_system(h::Hamiltonian, state_dimension::Int, backend)` - builds with known dimension +- `Flow(h::Hamiltonian; backend=AutoForwardDiff(), ...)` - high-level constructor for Hamiltonian flows +- `build_augmented_solution(result, sys, config)` - extracts (xf, pf, pvf) from augmented integration + +**Why:** +- Provides user-friendly scalar Hamiltonian interface (H instead of manually writing vector field) +- Uses DifferentiationInterface.jl for unified AD backend support (ForwardDiff, Zygote, etc.) +- Enables augmented costate computation without inefficient zero-dynamics integration +- Maintains flexibility: users can provide either Hamiltonian (with AD) or direct HamiltonianVectorField + +**What disappears:** None + +## Dependency Graph After Changes + +``` +Common (traits, configs) + โ†“ +Data (Hamiltonian, VectorField, HamiltonianVectorField) + โ†“ +Differentiation (AbstractADBackend, gradient functions) + โ†“ +Integrators (AbstractIntegrator) + โ†“ +Systems (HamiltonianSystem with rhs_augmented) + โ†“ +Flows (HamiltonianFlow with augment support) + โ†“ +Solutions (augmented solution builders) + โ†“ +Extensions (CTFlowsDifferentiationInterface, CTFlowsForwardDiff) +``` + +## Step 0 โ€” Branch + +```bash +git checkout develop && git pull +git checkout -b feature/hamiltonian-type +``` + +## Phase 1 โ€” Core Hamiltonian Type + +### Step 1 โ€” `src/Data/hamiltonian.jl` (new file) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Single Responsibility: Hamiltonian type only wraps scalar functions with traits, no gradient logic +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Follow VectorField/HamiltonianVectorField pattern for consistency + +- Define `abstract type AbstractHamiltonian{TD<:Common.TimeDependence, VD<:Common.VariableDependence, MD<:Common.AbstractMutabilityTrait} <: AbstractVectorField{TD, VD, MD}` +- Define `struct Hamiltonian{F<:Function, TD<:Common.TimeDependence, VD<:Common.VariableDependence, MD<:Common.AbstractMutabilityTrait} <: AbstractHamiltonian{TD, VD, MD}` +- Add field `f::F` - the scalar Hamiltonian function +- Implement internal helpers `_oop_arity_h` for each trait combination (2, 3, 3, 4) +- Implement `_detect_mutability_h` with PreconditionError for multiple methods, IncorrectArgument for invalid arity +- Implement constructor `Hamiltonian(f; is_autonomous, is_variable, is_inplace)` with auto-detection +- Implement 8 natural call signatures (OutOfPlace + InPlace ร— 4 trait combos) + - For in-place signatures: `h!(du, t, x, p)` where `du` is a scalar buffer (size 1 mutable variable) +- Implement 4 uniform call signatures `(t, x, p, v)` that forward to natural signatures +- Implement `Base.show` methods + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 2 โ€” `src/Data/Data.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add include and export in correct load order + +- Add `include(joinpath(@__DIR__, "hamiltonian.jl"))` after `hamiltonian_vector_field.jl` +- Add `export AbstractHamiltonian, Hamiltonian` to exports list + +### Step 3 โ€” Test Checkpoint: Data.Hamiltonian + +> ๐Ÿงช Applying Testing Rule โ€” Define fake types at module top-level, separate unit/integration/error tests +> ๐Ÿ”ฌ Applying Type-Stability Rule โ€” Test @inferred for constructors + +- Create `test/suite/data/test_hamiltonian.jl` +- Define fake types for testing +- Test sections: + - `@testset "Contract: NotImplemented errors"` - verify stubs throw correctly + - `@testset "Unit: Construction with all trait combinations"` - test all 4ร—2 combos + - `@testset "Unit: Mutability auto-detection"` - test arity detection + - `@testset "Unit: Natural call signatures"` - test all 8 signatures + - `@testset "Unit: Uniform call signatures"` - test (t, x, p, v) forwarding + - `@testset "Error: Multiple methods"` - verify PreconditionError + - `@testset "Error: Invalid arity"` - verify IncorrectArgument + - `@testset "Type Stability"` - @inferred on constructors + - `@testset "Exports"` - verify Hamiltonian is exported + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/data/test_hamiltonian"])' 2>&1 | tee /tmp/phase1.log +grep -E "Error|Fail|Test Summary" /tmp/phase1.log +``` + +## Phase 2 โ€” AD Backend Strategy Pattern + +### Step 4 โ€” `src/Differentiation/Differentiation.jl` (new file) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Module manifest following Integrators pattern + +- Define module docstring +- Import external packages: `import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES`, `import CTSolvers.Strategies` +- Include files in order: `abstract_ad_backend.jl`, `differentiation_interface.jl`, `building.jl` +- Export: `AbstractADBackend, DifferentiationInterface, build_ad_backend, AbstractCache, ADCache, gradient_hamiltonian_x, gradient_hamiltonian_x!, gradient_hamiltonian_p, gradient_hamiltonian_p!, gradient_hamiltonian_v, gradient_hamiltonian_v!, prepare_cache` + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 5 โ€” `src/Differentiation/abstract_ad_backend.jl` (new file) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Abstract type defines AD backend strategy contract with separated gradients and cache preparation +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Follow Integrators/abstract_integrator.jl pattern with stubs on abstract type + +- Define `abstract type AbstractADBackend <: CTSolvers.Strategies.AbstractStrategy end` +- Define `struct DifferentiationInterfaceTag <: Common.AbstractTag end` +- Define `abstract type AbstractCache end` +- Define `struct ADCache{PX, PP, PV} <: AbstractCache` with fields `prep_x::PX`, `prep_p::PP`, `prep_v::PV` +- Implement `function __default_ad_backend(::Type{<:Common.AbstractTag})` returning missing (for default backend resolution) +- **Contract stubs (all throw NotImplemented):** + - `gradient_hamiltonian_x(ad::AbstractADBackend, h::Data.AbstractHamiltonian, t, x, p, v)` โ†’ returns โˆ‚H/โˆ‚x + - `gradient_hamiltonian_x!(grad_x, ad::AbstractADBackend, h::Data.AbstractHamiltonian, t, x, p, v)` โ†’ in-place โˆ‚H/โˆ‚x + - `gradient_hamiltonian_p(ad::AbstractADBackend, h::Data.AbstractHamiltonian, t, x, p, v)` โ†’ returns โˆ‚H/โˆ‚p + - `gradient_hamiltonian_p!(grad_p, ad::AbstractADBackend, h::Data.AbstractHamiltonian, t, x, p, v)` โ†’ in-place โˆ‚H/โˆ‚p + - `gradient_hamiltonian_v(ad::AbstractADBackend, h::Data.AbstractHamiltonian, t, x, p, v)` โ†’ returns โˆ‚H/โˆ‚v + - `gradient_hamiltonian_v!(grad_v, ad::AbstractADBackend, h::Data.AbstractHamiltonian, t, x, p, v)` โ†’ in-place โˆ‚H/โˆ‚v + - `prepare_cache(ad::AbstractADBackend, h::Data.AbstractHamiltonian, typical_x, typical_p, typical_v)` โ†’ returns ADCache +- Note: Contract does NOT know about Constant context types (extension detail) +- Note: Gradients are SEPARATED by variable (x, p, v), not combined + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 6 โ€” `src/Differentiation/differentiation_interface.jl` (new file) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Concrete DifferentiationInterface strategy wraps DifferentiationInterface.jl backends +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Follow Integrators/sciml.jl pattern + +- Define `struct DifferentiationInterface{O<:CTSolvers.Strategies.StrategyOptions} <: AbstractADBackend` with field `options::O` +- Implement `CTSolvers.Strategies.id(::Type{<:DifferentiationInterface}) = :di` +- Implement `CTSolvers.Strategies.description(::Type{<:DifferentiationInterface})` +- Implement `CTSolvers.Strategies.metadata(::Type{<:DifferentiationInterface})` with options (backend, prepare) + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 7 โ€” `src/Differentiation/building.jl` (new file) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Builder function constructs AD backend strategy from user options +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Follow Integrators/building.jl pattern + +- Implement `function build_ad_backend(ad_backend)` that constructs DifferentiationInterface strategy from backend option + - If ad_backend is already an AbstractADBackend, return it + - If ad_backend is a DifferentiationInterface backend (e.g., AutoForwardDiff()), wrap it in DifferentiationInterface strategy + - If ad_backend is missing or nothing, use default DifferentiationInterface() which resolves via __default_ad_backend + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 8 โ€” `src/CTFlows.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add Differentiation module to load order + +- Add `include(joinpath(@__DIR__, "Differentiation", "Differentiation.jl"))` after Data, before Integrators +- Add `using .Differentiation` + +### Step 9 โ€” `ext/CTFlowsDifferentiationInterface.jl` (new file) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Extension implements AD strategy with separated gradients and Constant contexts (extension detail) + +- Check for DifferentiationInterface availability with `@require DifferentiationInterface` +- Implement `gradient_hamiltonian_x(ad::DifferentiationInterface, h::Data.AbstractHamiltonian{..., OutOfPlace}, t, x, p, v, cache::Union{ADCache, Nothing}=nothing)` + - If cache provided and cache.prep_x not nothing: `gradient(h_x, cache.prep_x, ad, x, Constant(p), Constant(v), Constant(t))` + - Otherwise: `gradient(h_x, ad, x, Constant(p), Constant(v), Constant(t))` where `h_x(x, p, v, t) = h(t, x, p, v)` + - Returns โˆ‚H/โˆ‚x +- Implement `gradient_hamiltonian_x!(grad_x, ad::DifferentiationInterface, h::Data.AbstractHamiltonian{..., InPlace}, t, x, p, v, cache::Union{ADCache, Nothing}=nothing)` + - Similar to OutOfPlace but in-place with gradient! +- Implement `gradient_hamiltonian_p(ad::DifferentiationInterface, h::Data.AbstractHamiltonian{..., OutOfPlace}, t, x, p, v, cache::Union{ADCache, Nothing}=nothing)` + - If cache provided and cache.prep_p not nothing: `gradient(h_p, cache.prep_p, ad, p, Constant(x), Constant(v), Constant(t))` + - Otherwise: `gradient(h_p, ad, p, Constant(x), Constant(v), Constant(t))` where `h_p(p, x, v, t) = h(t, x, p, v)` + - Returns โˆ‚H/โˆ‚p +- Implement `gradient_hamiltonian_p!(grad_p, ad::DifferentiationInterface, h::Data.AbstractHamiltonian{..., InPlace}, t, x, p, v, cache::Union{ADCache, Nothing}=nothing)` + - Similar in-place version +- Implement `gradient_hamiltonian_v(ad::DifferentiationInterface, h::Data.AbstractHamiltonian{..., OutOfPlace}, t, x, p, v, cache::Union{ADCache, Nothing}=nothing)` + - If cache provided and cache.prep_v not nothing: `gradient(h_v, cache.prep_v, ad, v, Constant(x), Constant(p), Constant(t))` + - Otherwise: `gradient(h_v, ad, v, Constant(x), Constant(p), Constant(t))` where `h_v(v, x, p, t) = h(t, x, p, v)` + - Returns โˆ‚H/โˆ‚v +- Implement `gradient_hamiltonian_v!(grad_v, ad::DifferentiationInterface, h::Data.AbstractHamiltonian{..., InPlace}, t, x, p, v, cache::Union{ADCache, Nothing}=nothing)` + - Similar in-place version +- Implement `prepare_cache(ad::DifferentiationInterface, h::Data.AbstractHamiltonian, typical_x, typical_p, typical_v)` + - Use `prepare_gradient` with Constant contexts for inactive arguments + - For โˆ‚H/โˆ‚x: `prepare_gradient(h_x, ad, typical_x, Constant(typical_p), Constant(typical_v), Constant(t))` โ†’ prep_x + - For โˆ‚H/โˆ‚p: `prepare_gradient(h_p, ad, typical_p, Constant(typical_x), Constant(typical_v), Constant(t))` โ†’ prep_p + - For โˆ‚H/โˆ‚v: `prepare_gradient(h_v, ad, typical_v, Constant(typical_x), Constant(typical_p), Constant(t))` โ†’ prep_v + - Return `ADCache(prep_x, prep_p, prep_v)` + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 10 โ€” `ext/CTFlowsForwardDiff.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Existing extension adds default backend resolution + +- Add `function __default_ad_backend(::Type{DifferentiationInterfaceTag})` returning AutoForwardDiff() +- This provides the default AD backend when ForwardDiff is loaded (like CTFlowsOrdinaryDiffEqTsit5 provides default Tsit5 algorithm) + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 11 โ€” Test Checkpoint: AD Backend Strategy + +> ๐Ÿงช Applying Testing Rule - Test strategy contract and extension pattern + +- Create `test/suite/differentiation/test_ad_backend.jl` +- Test sections: + - `@testset "Contract: CTSolvers.Strategies methods"` - id, description, metadata + - `@testset "Unit: DifferentiationInterface construction"` - with options + - `@testset "Unit: build_ad_backend"` - wrapping raw backends, returning existing strategies + - `@testset "Error: NotImplemented without extension"` - stub throws correctly + - `@testset "Integration: gradient_hamiltonian_x with DifferentiationInterface"` - numerical verification + - `@testset "Integration: gradient_hamiltonian_p with DifferentiationInterface"` - numerical verification + - `@testset "Integration: gradient_hamiltonian_v with DifferentiationInterface"` - numerical verification + - `@testset "Integration: prepare_cache with DifferentiationInterface"` - cache creations AutoForwardDiff() + - `@testset "Exports"` - verify AD backend types and functions exported + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/differentiation/test_ad_backend"])' 2>&1 | tee /tmp/phase2.log +grep -E "Error|Fail|Test Summary" /tmp/phase2.log +``` + +## Phase 3 โ€” Hamiltonian to VectorField Conversion (in Differentiation module) + +### Step 12 โ€” `src/Differentiation/Differentiation.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add include for conversion file + +- Add `include(joinpath(@__DIR__, "conversions.jl"))` after `building.jl` +- Add `export build_hamiltonian_vector_field, hamiltonian_vector_field` to exports list + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 13 โ€” `src/Differentiation/conversions.jl` (new file) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Generic conversion functions use AD backend contract to build HamiltonianVectorField +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Moved from Data to Differentiation to respect dependency order + +- Implement `function build_hamiltonian_vector_field(h::Data.AbstractHamiltonian{..., OutOfPlace}, ad::AbstractADBackend, cache::Union{ADCache, Nothing}=nothing)` + - Call `gradient_hamiltonian_x(ad, h, t, x, p, v, cache)` for โˆ‚H/โˆ‚x + - Call `gradient_hamiltonian_p(ad, h, t, x, p, v, cache)` for โˆ‚H/โˆ‚p + - Return `HamiltonianVectorField{..., OutOfPlace}` with function `(t, x, p, v) -> (-gradient_hamiltonian_x(...), gradient_hamiltonian_p(...))` +- Implement `function build_hamiltonian_vector_field(h::Data.AbstractHamiltonian{..., InPlace}, ad::AbstractADBackend, cache::Union{ADCache, Nothing}=nothing)` + - Similar to OutOfPlace but with in-place gradient calls + - Return `HamiltonianVectorField{..., InPlace}` with in-place gradient calls +- Implement user-friendly wrapper `function hamiltonian_vector_field(h::Data.AbstractHamiltonian; ad_backend=nothing, cache=nothing)` + - Build AD backend from ad_backend option using `build_ad_backend(ad_backend)` + - Call internal `build_hamiltonian_vector_field(h, ad, cache)` + - Returns HamiltonianVectorField +- Note: This is internal API, analogous to `build_flow(system, integrator)` +- Note: cache argument defaults to nothing, can be provided if pre-computed + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 14 โ€” Test Checkpoint: Hamiltonian Conversions + +> ๐Ÿงช Applying Testing Rule - Test conversion from Hamiltonian to HamiltonianVectorField using AD backend contract + +- Create `test/suite/differentiation/test_hamiltonian_conversions.jl` +- Test sections: + - `@testset "Unit: build_hamiltonian_vector_field (OutOfPlace)"` - returns OutOfPlace HamiltonianVectorField + - `@testset "Unit: build_hamiltonian_vector_field (InPlace)"` - returns InPlace HamiltonianVectorField + - `@testset "Unit: hamiltonian_vector_field user-friendly wrapper"` - with ad_backend kwarg + - `@testset "Integration: H -> Hv -> call (OutOfPlace)"` - end-to-end with simple Hamiltonian + - `@testset "Integration: H -> Hv -> call (InPlace)"` - end-to-end with in-place Hamiltonian + - `@testset "Integration: Separated gradients correctness"` - verify โˆ‚H/โˆ‚x and โˆ‚H/โˆ‚p separately + - `@testset "Integration: Cache usage"` - verify cache passed through to gradient calls + - `@testset "Integration: Multiple backends"` - test with DifferentiationInterface + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/differentiation/test_hamiltonian_conversions"])' 2>&1 | tee /tmp/phase3.log +grep -E "Error|Fail|Test Summary" /tmp/phase3.log +``` + +## Phase 4 โ€” HamiltonianSystem Cache Field + +### Step 16 โ€” `src/Systems/hamiltonian_system.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Add cache field for gradient preparation (Systems decoupled from AD) +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add new type parameter at end for type stability + +- Add type parameter `CACHE` to `HamiltonianSystem` struct definition (after FINRHS) +- Add field `cache::CACHE` (opaque cache object containing prep_x, prep_p, prep_v, can be Nothing) +- Modify all 4 constructors to accept `cache=nothing` +- Update `Base.show` to display cache status + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched. + +### Step 17 โ€” Test Checkpoint: HamiltonianSystem Cache Field + +> ๐Ÿงช Applying Testing Rule - Test cache field construction + +- Create `test/suite/systems/test_hamiltonian_system_cache.jl` +- Test sections: + - `@testset "Unit: HamiltonianSystem with cache=nothing"` - default construction + - `@testset "Unit: HamiltonianSystem with cache object"` - with opaque cache + - `@testset "Type Stability"` - verify CACHE parameter is type-stable + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems/test_hamiltonian_system_cache"])' 2>&1 | tee /tmp/phase4.log +grep -E "Error|Fail|Test Summary" /tmp/phase4.log +``` + +## Phase 5 โ€” Hamiltonian to System (without augmentation) + +### Step 18 โ€” `src/Systems/building.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Build system from Hamiltonian with AD backend (cache=nothing initially) +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Factory functions follow existing build_system pattern + +- Add `function build_system(h::Data.Hamiltonian, ad::Differentiation.AbstractADBackend)` + - Call `build_hamiltonian_vector_field(h, ad)` to get HamiltonianVectorField + - Call `HamiltonianSystem(hvf)` with `cache=nothing` + - Return HamiltonianSystem +- Add `function build_system(h::Data.Hamiltonian, state_dimension::Int, ad::Differentiation.AbstractADBackend)` + - Call `build_hamiltonian_vector_field(h, ad)` to get HamiltonianVectorField + - Call `HamiltonianSystem(hvf, state_dimension)` with `cache=nothing` + - Return HamiltonianSystem + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 19 โ€” Test Checkpoint: Hamiltonian to System + +> ๐Ÿงช Applying Testing Rule - Test Hamiltonian -> System conversion + +- Create `test/suite/systems/test_hamiltonian_to_system.jl` +- Test sections: + - `@testset "Unit: build_system from Hamiltonian"` - with and without state_dimension + - `@testset "Integration: H -> Hv -> System"` - end-to-end with simple Hamiltonian + - `@testset "Integration: H -> System -> RHS"` - verify RHS works without cache + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems/test_hamiltonian_to_system"])' 2>&1 | tee /tmp/phase5.log +grep -E "Error|Fail|Test Summary" /tmp/phase5.log +``` + +## Phase 6 โ€” Cache Preparation in Call Pipeline + +### Step 20 โ€” `src/Differentiation/preparation.jl` (new file) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” prepare_cache creates ADCache with prep_x, prep_p, prep_v for gradient optimization +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Two surcharges: from Hamiltonian and from System + +- Implement `function prepare_cache(h::Data.AbstractHamiltonian, ad::AbstractADBackend, typical_x, typical_p, typical_v)` + - Calls contract method `prepare_cache(ad, h, typical_x, typical_p, typical_v)` + - Returns ADCache +- Implement `function prepare_cache(sys::Systems.HamiltonianSystem, ad::AbstractADBackend, config::Common.AbstractConfig; variable)` + - Extract `typical_x`, `typical_p` from config initial conditions + - Extract `typical_v` from config variable if NonFixed + - Extract `h` from `sys.hvf` (the original Hamiltonian) + - Call `prepare_cache(h, ad, typical_x, typical_p, typical_v)` + - Returns ADCache +- Note: These functions are internal, used by Flows.call pipeline +- Note: Contract method is implemented in extension (e.g., CTFlowsDifferentiationInterface) +- Note: Extension uses Constant contexts for inactive arguments during preparation (extension detail) + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 21 โ€” `src/Differentiation/Differentiation.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add include for preparation + +- Add `include(joinpath(@__DIR__, "preparation.jl"))` after `conversions.jl` +- Add `export prepare_cache` to exports list (already exported from abstract_ad_backend.jl) + +### Step 22 โ€” `src/Flows/flow.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” HamiltonianFlow stores AD backend for cache preparation + +- Add field `ad_backend::Union{Differentiation.AbstractADBackend, Nothing}` to `HamiltonianFlow` struct +- Modify `HamiltonianFlow` constructors to accept optional `ad_backend=nothing` +- Update `Base.show` to display AD backend status + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched. + +### Step 23 โ€” `src/Flows/calling.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Prepare cache in call pipeline when AD backend present + +- Modify `call` function for HamiltonianFlow to add cache preparation: + ```julia + function call(flow::HamiltonianFlow, config::Common.AbstractConfig; variable, unsafe) + sys = system(flow) + int = integrator(flow) + ad = ad_backend(flow) + + # Prepare cache if AD backend present + cache = !isnothing(ad) ? Differentiation.prepare_cache(sys, ad, config; variable=variable) : nothing + + # Build problem with cache in parameters + prob = Integrators.build_problem(int, sys, config; variable=variable, cache=cache) + opts = Integrators.build_options(int, config) + result = Integrators.solve_problem(int, prob, opts; unsafe=unsafe) + flow_sol = Solutions.build_solution(result, config) + return flow_sol + end + ``` + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 24 โ€” `src/Systems/hamiltonian_system.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” RHS functions use ADCache with separated gradients when available +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Modify _build_rhs to check cache and use gradient with prep + +- Modify `_build_rhs` for OutOfPlace to check `ฮป.cache`: + - If `!isnothing(ฮป.cache)`: use `gradient_hamiltonian_x(ad, h, t, x, p, v, ฮป.cache)` and similarly for โˆ‚H/โˆ‚p + - If `isnothing(ฮป.cache)`: use gradient without cache (fallback) +- Modify `_build_rhs` for InPlace similarly +- Modify `_build_oop_rhs` similarly for out-of-place versions +- Note: Cache is ADCache with prep_x, prep_p, prep_v fields +- Note: RHS uses separated gradient methods (gradient_hamiltonian_x, gradient_hamiltonian_p) + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 25 โ€” `ext/CTFlowsSciML.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Pass ADCache through ODE parameters + +- Modify `ODEParameters` struct to include `cache::Union{Differentiation.ADCache, Nothing}` field +- Modify `ode_problem` construction to accept and pass `cache` parameter +- Ensure cache is accessible in RHS via `p.cache` + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 26 โ€” Test Checkpoint: Cache Preparation Pipeline + +> ๐Ÿงช Applying Testing Rule - Test cache preparation with ADCache and usage in call pipeline + +- Create `test/suite/differentiation/test_cache_preparation.jl` +- Test sections: + - `@testset "Unit: prepare_cache from Hamiltonian"` - verify ADCache structure + - `@testset "Unit: prepare_cache from System"` - extracts typical values correctly + - `@testset "Integration: cache preparation in call"` - full pipeline with cache + - `@testset "Integration: RHS uses ADCache when available"` - verify optimized gradient calls + - `@testset "Integration: fallback without cache"` - verify non-cached path works + - `@testset "Integration: cache passed through ODE parameters"` - verify p.cache accessibility + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/differentiation/test_cache_preparation"])' 2>&1 | tee /tmp/phase6.log +grep -E "Error|Fail|Test Summary" /tmp/phase6.log +``` + +## Phase 7 โ€” Augmented Configs and Solutions + +### Step 27 โ€” `src/Common/configs.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add new config types following existing pattern + +- Add `struct HamiltonianAugmentedPointConfig{T0<:Real, X0, P0, TF<:Real} <: AbstractConfigWithMaC{X0, PointTrait, HamiltonianTrait}` +- Add `struct HamiltonianAugmentedTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0, P0} <: AbstractConfigWithMaC{X0, TrajectoryTrait, HamiltonianTrait}` +- Implement `Base.show` methods for both configs +- Implement `initial_condition` to return `vcat(x0, p0, zeros(m))` (m inferred at runtime) + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 28 โ€” `src/Solutions/building.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” Augmented solution builder handles result extraction for augmented flows + +- Implement `_aug_split_solution(u, x0, p0, m)` helper (splits [x; p; pv] into components) +- Implement `function build_augmented_solution(result, sys::HamiltonianSystem, config::HamiltonianAugmentedPointConfig)` +- Return tuple `(xf, pf, pvf)` with appropriate types (scalar/vector based on dimensions) +- Implement `function build_augmented_solution(result, sys::HamiltonianSystem, config::HamiltonianAugmentedTrajectoryConfig)` +- Return wrapped result with augmented accessor methods + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 29 โ€” Test Checkpoint: Augmented Configs and Solutions + +> ๐Ÿงช Applying Testing Rule - Test config construction and solution extraction + +- Create `test/suite/common/test_augmented_configs.jl` +- Test sections: + - `@testset "Unit: HamiltonianAugmentedPointConfig construction"` - with scalar/vector dimensions + - `@testset "Unit: HamiltonianAugmentedTrajectoryConfig construction"` - with scalar/vector dimensions + - `@testset "Unit: initial_condition for augmented configs"` - correct zero-padding + - `@testset "Unit: build_augmented_solution point"` - correct splitting + - `@testset "Unit: build_augmented_solution trajectory"` - correct wrapping + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/common/test_augmented_configs"])' 2>&1 | tee /tmp/phase7.log +grep -E "Error|Fail|Test Summary" /tmp/phase7.log +``` + +## Phase 8 โ€” Flow-Level Augment Support + +### Step 30 โ€” `src/Flows/flow.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” augment parameter on flow call is most coherent with existing variable/unsafe pattern + +- Modify `HamiltonianFlow` callable methods to add `augment::Bool=false` keyword argument +- In both point and trajectory callables: if `augment=true`, build `HamiltonianAugmentedPointConfig` or `HamiltonianAugmentedTrajectoryConfig` +- Add validation: if `augment=true` and system is Fixed, throw IncorrectArgument +- Add validation: if `augment=true` and `rhs_augmented===nothing`, throw PreconditionError + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched. + +### Step 31 โ€” `src/Flows/calling.jl` (modified) + +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Add dispatch for augmented configs + +- Add method `function call(flow::HamiltonianFlow, config::HamiltonianAugmentedPointConfig; variable, unsafe)` +- Build augmented initial condition `u0 = vcat(x0, p0, zeros(m))` +- Use `rhs_augmented` from system instead of `rhs` +- Call `build_augmented_solution` instead of `build_solution` +- Add similar method for `HamiltonianAugmentedTrajectoryConfig` + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 32 โ€” Test Checkpoint: Augmented Flow Integration + +> ๐Ÿงช Applying Testing Rule - Test augment=true flow calls with numerical verification + +- Create `test/suite/flows/test_hamiltonian_augmented.jl` +- Test sections: + - `@testset "Error: augment=true on Fixed system"` - IncorrectArgument + - `@testset "Error: augment=true without Hamiltonian source"` - PreconditionError + - `@testset "Integration: augment=true point call"` - verify pvf = -โˆซโˆ‚H/โˆ‚v dt numerically + - `@testset "Integration: augment=true trajectory call"` - verify augmented solution structure + - `@testset "Integration: Scalar/vector dimensions"` - correct return types + - `@testset "Regression: augment=false still works"` - ensure non-augmented path unchanged + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/flows/test_hamiltonian_augmented"])' 2>&1 | tee /tmp/phase8.log +grep -E "Error|Fail|Test Summary" /tmp/phase8.log +``` + +## Phase 9 โ€” High-Level Flow Constructor + +### Step 33 โ€” `src/Flows/building.jl` (modified) + +> ๐Ÿ“‹ Applying Architecture Rule โ€” User-friendly Flow constructor builds strategy internally, follows Flow(data::Data.VectorField; opts...) pattern +> ๐Ÿ—๏ธ Applying Modules Rule โ€” Two-level API: internal build functions + user-friendly constructors + +- Implement `function Flow(h::AbstractHamiltonian; ad_backend=DifferentiationInterface(), integrator_opts...)` + - Build AD backend strategy from ad_backend option using `Differentiation.build_ad_backend(ad_backend)` + - Build HamiltonianVectorField via internal `build_hamiltonian_vector_field(h, ad_backend)` + - Build HamiltonianSystem via `Systems.build_system(hvf)` + - Build integrator via `Integrators.build_integrator(; integrator_opts...)` + - Return `build_flow(system, integrator, ad_backend)` (pass ad_backend to flow) + - Note: If ad_backend is not provided, uses default DifferentiationInterface() which resolves to AutoForwardDiff() via CTFlowsForwardDiff extension + - Note: This is user-friendly API that hides strategy construction details, analogous to `Flow(data::Data.VectorField; opts...)` +- Implement overload with `state_dimension::Int` parameter +- Modify `build_flow` to accept optional `ad_backend` argument + +> โ›” Do NOT write docstrings in this step. Leave TODO comments only. + +### Step 34 โ€” Test Checkpoint: High-Level Flow Constructor + +> ๐Ÿงช Applying Testing Rule - Test Flow(H) convenience constructor + +- Create `test/suite/flows/test_flow_from_hamiltonian.jl` +- Test sections: + - `@testset "Unit: Flow(H) construction"` - with ad_backend option + - `@testset "Unit: Flow(H, n) construction"` - with dimension + - `@testset "Integration: Full pipeline H -> Flow -> call"` - end-to-end + - `@testset "Integration: Flow(H) with augment=true"` - complete workflow + - `@testset "Integration: Default backend resolution"` - uses AutoForwardDiff() when ForwardDiff loaded + +Run tests: +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/flows/test_flow_from_hamiltonian"])' 2>&1 | tee /tmp/phase9.log +grep -E "Error|Fail|Test Summary" /tmp/phase9.log +``` + +## Phase 10 โ€” Documentation + +### Step 35 โ€” Docstrings (all modified files) + +> ๐Ÿ“š Applying Documentation Rule โ€” Use $(TYPEDEF)/$(TYPEDSIGNATURES), full sections, [@ref]/[@extref] cross-references, safe examples + +Write docstrings for: +- `src/Data/hamiltonian.jl` - Hamiltonian struct, constructor, call signatures, helpers +- `src/Differentiation/abstract_ad_backend.jl` - AbstractADBackend, DifferentiationInterfaceTag +- `src/Differentiation/differentiation_interface.jl` - DifferentiationInterface strategy +- `src/Differentiation/gradient_functions.jl` - gradient function stubs +- `src/Differentiation/building.jl` - build_ad_backend +- `src/Differentiation/preparation.jl` - prepare_cache with Constant contexts +- `ext/CTFlowsDifferentiationInterface.jl` - extension implementations +- `src/Data/hamiltonian_conversions.jl` - build_hamiltonian_vector_field with Context types and separated gradients +- `src/Systems/building.jl` - build_system overloads for Hamiltonian +- `src/Systems/hamiltonian_system.jl` - cache field documentation +- `src/Common/configs.jl` - augmented config types +- `src/Flows/building.jl` - Flow(H) constructors +- `src/Flows/flow.jl` - ad_backend field documentation +- `src/Flows/calling.jl` - cache preparation and augment parameter documentation +- `src/Solutions/building.jl` - build_augmented_solution + +> ๐Ÿ“– Applying Documentation Rule โ€” Update docs/api_reference.jl and docs/make.jl if new submodules introduced + +## Step 36 โ€” Final Test Run + +> โ–ถ๏ธ Applying Testing Execution Rule - Standard test command + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/hamiltonian_feature.log +grep -E "Error|Fail|Test Summary" /tmp/hamiltonian_feature.log +``` + +Expected: all test suites pass, zero failures, zero errors. + +## Files Summary + +**New**: +- `src/Data/hamiltonian.jl` +- `src/Differentiation/Differentiation.jl` +- `src/Differentiation/abstract_ad_backend.jl` +- `src/Differentiation/differentiation_interface.jl` +- `src/Differentiation/building.jl` +- `src/Differentiation/conversions.jl` +- `src/Differentiation/preparation.jl` +- `ext/CTFlowsDifferentiationInterface.jl` +- `test/suite/data/test_hamiltonian.jl` +- `test/suite/differentiation/test_ad_backend.jl` +- `test/suite/differentiation/test_hamiltonian_conversions.jl` +- `test/suite/differentiation/test_cache_preparation.jl` +- `test/suite/systems/test_hamiltonian_system_cache.jl` +- `test/suite/systems/test_hamiltonian_to_system.jl` +- `test/suite/common/test_augmented_configs.jl` +- `test/suite/flows/test_hamiltonian_augmented.jl` +- `test/suite/flows/test_flow_from_hamiltonian.jl` + +**Modified**: +- `src/Data/Data.jl` - includes and exports (Hamiltonian only) +- `src/CTFlows.jl` - add Differentiation module to load order +- `ext/CTFlowsForwardDiff.jl` - add default backend resolution +- `src/Systems/hamiltonian_system.jl` - cache field and constructors +- `src/Systems/building.jl` - build_system overloads for Hamiltonian +- `src/Common/configs.jl` - augmented config types +- `src/Flows/flow.jl` - ad_backend field, augment parameter +- `src/Flows/calling.jl` - cache preparation, augmented config dispatch +- `src/Solutions/building.jl` - augmented solution builder +- `src/Flows/building.jl` - Flow(H) constructors +- `ext/CTFlowsSciML.jl` - ADCache field in ODEParameters + +**Deleted**: None + +**Dependencies**: +- Add DifferentiationInterface to Project.toml dependencies diff --git a/reports/hamilonian/save/scalar_vector/design_doc.md b/reports/hamilonian/save/scalar_vector/design_doc.md new file mode 100644 index 00000000..33da1c57 --- /dev/null +++ b/reports/hamilonian/save/scalar_vector/design_doc.md @@ -0,0 +1,133 @@ +# Synthรจse design : gestion des formes et construction des RHS + +## Objectifs + +1. **Cohรฉrence des formes** : si l'utilisateur passe un scalaire, il rรฉcupรจre un scalaire ; un vecteur de taille 1 โ†’ vecteur de taille 1 ; etc. +2. **ร‰conomie de closures** : ne construire que la closure effectivement utilisรฉe lors d'un appel de flot. +3. **Dรฉcouplage construction / appel** : la construction du flot ne nรฉcessite aucune information sur les conditions initiales. +4. **Centralisation de la dรฉcision** : c'est `build_problem` qui choisit quelle closure construire, en fonction de `ismutable(u0)`. + +--- + +## Les trois systรจmes + +| Systรจme | Split `u โ†’ (x, p)` ? | Coerce scalaire ? | Quand construire le RHS | +|---|---|---|---| +| `VectorFieldSystem` | Non โ€” `u = x` | Non nรฉcessaire | ร€ la construction (pas de split ni de coerce) | +| `HamiltonianVectorFieldSystem` | Oui โ€” `u = [x; p]` | Oui | Paresseux dans `build_problem` | +| `HamiltonianSystem` (AD) | Oui โ€” `u = [x; p]` (ou `[x; p; pv]`) | Oui | Paresseux dans `build_problem` | + +--- + +## Helpers communs aux systรจmes hamiltoniens + +### `_state_dim(x0)` +``` +Number โ†’ 1 +AbstractVector โ†’ length(x0) +AbstractMatrix โ†’ size(x0, 1) +``` + +### `_make_coerce(x0)` โ€” appliquรฉ aux vues **en entrรฉe** du HVF uniquement +``` +Number โ†’ only # 1-element SubArray โ†’ scalaire +AbstractVector โ†’ identity +AbstractMatrix โ†’ identity +``` + +### `_ham_split(u, N)` โ€” toujours des vues, N toujours Int +```julia +_ham_split(u::AbstractVector, N::Int) = @view(u[1:N]), @view(u[N+1:2N]) +_ham_split(u::AbstractMatrix, N::Int) = @view(u[1:N, :]), @view(u[N+1:2N, :]) +``` +`N = nothing` disparaรฎt : il est toujours connu ร  la construction paresseuse. + +--- + +## Rรจgle d'asymรฉtrie : entrรฉes vs sorties (cas HamiltonianVectorFieldSystem) + +| | Entrรฉes HVF (`x`, `p`) | Sorties HVF (`dx`, `dp`) | +|---|---|---| +| OOP HVF | coercรฉes (scalaire si N=1) | retournรฉes telles quelles | +| IP HVF, `u0` mutable | coercรฉes | **jamais coercรฉes** โ€” vues mutables 1D | +| IP HVF, `u0` immutable | coercรฉes | buffers allouรฉs, rรฉsultat converti via `typeof(u)(...)` | + +Pour `HamiltonianSystem` (AD), la sortie du gradient est toujours un vecteur : pas de cas IP. + +--- + +## Arbre de dรฉcision dans `build_problem` + +``` +ismutable(u0) ? +โ”‚ +โ”œโ”€ oui โ”€โ”€โ†’ build_rhs(sys, x0, p0) โ†’ ODEProblem(f!, u0, tspan, ฮป) +โ”‚ (closure in-place) +โ”‚ +โ””โ”€ non โ”€โ”€โ†’ build_oop_rhs(sys, x0, p0) โ†’ ODEProblem(f, u0, tspan, ฮป) + (closure out-of-place) +``` + +Pour `VectorFieldSystem` (RHS prรฉcompilรฉs) : +``` +ismutable(u0) ? +โ”œโ”€ oui โ”€โ”€โ†’ rhs(sys) โ†’ ODEProblem(f!, u0, tspan, ฮป) +โ””โ”€ non โ”€โ”€โ†’ rhs_oop(sys, false) โ†’ ODEProblem(f, u0, tspan, ฮป) +``` + +--- + +## VectorFieldSystem โ€” pourquoi pas lazy ? + +Pas de split `u โ†’ (x, p)`, pas de coerce : le RHS ne dรฉpend pas de la forme de `x0`. +Les trois closures (in-place, oop, oop-finalize) sont construites une fois ร  la +construction et le coรปt est nรฉgligeable. + +La gestion scalaire est naturelle : +- `x0 :: Number` โ†’ `ismutable = false` โ†’ chemin oop โ†’ `vf(t, u, v)` retourne un scalaire โœ“ +- `x0 :: SVector` โ†’ `ismutable = false` โ†’ `rhs_oop_finalize` โ†’ `typeof(u)(dx)` โœ“ +- `x0 :: Vector` โ†’ chemin in-place โœ“ + +โš ๏ธ Combinaison non supportรฉe : VF **in-place** + `x0 :: Number`. +`similar(::Number)` รฉchoue. Doit รชtre dรฉtectรฉe ร  la construction avec un `@warn` clair. + +--- + +## HamiltonianVectorFieldSystem โ€” lazy obligatoire + +Le split et le coerce dรฉpendent de la forme de `x0`/`p0`. +Une seule closure construite par appel de flot ; recompilรฉe une fois par combinaison +de types `(typeof(x0), typeof(p0))` grรขce au dispatch Julia. + +--- + +## HamiltonianSystem (AD) โ€” lazy, cas augmentรฉ + +Pour le cas non-augmentรฉ, mรชme logique que `HamiltonianVectorFieldSystem`. + +Pour le cas augmentรฉ (`u = [x; p; pv]`) : +- `pv` est la costate de la variable `v` +- `u0 = vcat(x0, p0, pv0)` est toujours un `Vector` mutable (pas de SVector) +- **Cas matriciel non supportรฉ** : si `x0` est une matrice (batch), la signification + de `pv` devient ambiguรซ (un `pv` par colonne ?). Ce cas est bloquรฉ explicitement + avec une `Exceptions.NotImplemented`. + +--- + +## Rรฉsumรฉ des fonctions publiques par systรจme + +| Systรจme | Fonctions exposรฉes | +|---|---| +| `VectorFieldSystem` | `rhs(sys)`, `rhs_oop(sys, is_mutable)` | +| `HamiltonianVectorFieldSystem` | `build_rhs(sys, x0, p0)`, `build_oop_rhs(sys, x0, p0)` | +| `HamiltonianSystem` | `build_rhs(sys, x0, p0)`, `build_oop_rhs(sys, x0, p0)`, `build_rhs_augmented(sys, n_x, n_v)` | + +--- + +## Ce qui change dans `build_problem` + +**Avant** : `sys` stocke 3 closures ; `build_problem` appelle `rhs(sys)` ou `rhs_oop(sys, bool)`. +`bool` est un flag indirect qui encode la mutabilitรฉ de `u0` sans y avoir accรจs. + +**Aprรจs** : `build_problem` reรงoit `config` (donc `x0`, `p0`, `u0`) et dรฉcide lui-mรชme +de construire la seule closure nรฉcessaire. Le flag `is_u0_mutable` disparaรฎt de l'API. diff --git a/reports/hamilonian/save/scalar_vector/hs_system.jl b/reports/hamilonian/save/scalar_vector/hs_system.jl new file mode 100644 index 00000000..ce8af4d7 --- /dev/null +++ b/reports/hamilonian/save/scalar_vector/hs_system.jl @@ -0,0 +1,262 @@ +# ============================================================================= +# HamiltonianSystem (AD-based, scalar Hamiltonian + gradient via backend) +# ============================================================================= +# +# Design rationale +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Same split / coerce strategy as HamiltonianVectorFieldSystem. +# No InPlace/OutOfPlace distinction: the Hamiltonian h is always scalar-valued, +# gradients are computed by the AD backend and always returned as plain vectors. +# +# Non-augmented case u = [x; p] +# โ†’ build_rhs / build_oop_rhs, lazy in build_problem +# +# Augmented case u = [x; p; pv] (variable costate) +# โ†’ build_rhs_augmented(sys, n_x, n_v), already lazy (called from build_problem) +# โ†’ matrix batch mode (x0 :: AbstractMatrix) is NOT supported here because +# the meaning of pv per column is undefined for the current AD backends. +# An explicit error is raised in build_problem. +# ============================================================================= + +struct HamiltonianSystem{ + N, + F <: Function, + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, + BACKEND <: Differentiation.AbstractADBackend, + RHS <: Function, + OOPROHS <: Function, +} <: AbstractHamiltonianSystem{TD, VD, Traits.WithAD} + h ::Data.Hamiltonian{F, TD, VD} + backend ::BACKEND + rhs ::RHS # kept for non-lazy callers that don't have x0/p0 handy + rhs_oop ::OOPROHS +end + +# N is kept as a type parameter for the non-lazy pre-built path (legacy). +# For the lazy path N is determined from x0 at build_problem time. + +function HamiltonianSystem(h::Data.Hamiltonian{F,TD,VD}, + backend::Differentiation.AbstractADBackend; + state_dimension::Union{Int,Nothing}=Common.__state_dimension()) where {F,TD,VD} + rhs = _build_ip_rhs_hs(h, backend, state_dimension) + rhs_oop = _build_oop_rhs_hs(h, backend, state_dimension) + return HamiltonianSystem{state_dimension, F, TD, VD, typeof(backend), + typeof(rhs), typeof(rhs_oop)}(h, backend, rhs, rhs_oop) +end + +hamiltonian(sys::HamiltonianSystem) = sys.h +backend(sys::HamiltonianSystem) = sys.backend +state_dimension(::HamiltonianSystem{N}) where N = N + +# ============================================================================= +# _ham_split / _ham_assign! and IC helpers +# (shared with HamiltonianVectorFieldSystem; defined in the same module) +# ============================================================================= +# _state_dim, _make_coerce, _ham_split, _ham_assign! โ€” see HamiltonianVectorFieldSystem file. + +# Additional split for the augmented state u = [x; p; pv] +_aug_split(u::AbstractVector, nx::Int, nv::Int) = ( + @view(u[1:nx]), + @view(u[nx+1:2nx]), + @view(u[end-nv+1:end]), +) +_aug_split(u::AbstractMatrix, nx::Int, nv::Int) = ( + @view(u[1:nx, :]), + @view(u[nx+1:2nx, :]), + @view(u[end-nv+1:end, :]), +) + +_aug_assign!(du::AbstractVector, dx, dp, dpv, nx::Int, nv::Int) = ( + du[1:nx] .= dx; du[nx+1:2nx] .= dp; du[end-nv+1:end] .= dpv +) +_aug_assign!(du::AbstractMatrix, dx, dp, dpv, nx::Int, nv::Int) = ( + du[1:nx,:] .= dx; du[nx+1:2nx,:] .= dp; du[end-nv+1:end,:] .= dpv +) + +# ============================================================================= +# Non-augmented RHS builders (legacy pre-built path, N may be Int or nothing) +# ============================================================================= + +function _build_ip_rhs_hs(h, backend, N) + return function (du, u, ฮป, t) + x, p = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient( + backend, h, t, x, p, Common.variable(ฮป), Common.cache(ฮป)) + _ham_assign!(du, โˆ‚p, -โˆ‚x, N) + return nothing + end +end + +function _build_oop_rhs_hs(h, backend, N) + return function (u, ฮป, t) + x, p = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient( + backend, h, t, x, p, Common.variable(ฮป), Common.cache(ฮป)) + return vcat(โˆ‚p, -โˆ‚x) + end +end + +# ============================================================================= +# Lazy non-augmented RHS builders (called from build_problem with x0, p0) +# ============================================================================= +# +# These supersede the pre-built closures when x0/p0 are available. +# Coerce is applied to inputs only; gradients are always plain vectors. + +function build_rhs(sys::HamiltonianSystem, x0, p0) + h, backend = sys.h, sys.backend + N, cx, cp = _state_dim(x0), _make_coerce(x0), _make_coerce(p0) + return function (du, u, ฮป, t) + xv, pv = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient( + backend, h, t, cx(xv), cp(pv), Common.variable(ฮป), Common.cache(ฮป)) + _ham_assign!(du, โˆ‚p, -โˆ‚x, N) + return nothing + end +end + +function build_oop_rhs(sys::HamiltonianSystem, x0, p0) + h, backend = sys.h, sys.backend + N, cx, cp = _state_dim(x0), _make_coerce(x0), _make_coerce(p0) + return function (u, ฮป, t) + xv, pv = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient( + backend, h, t, cx(xv), cp(pv), Common.variable(ฮป), Common.cache(ฮป)) + return typeof(u)(vcat(โˆ‚p, -โˆ‚x)) # preserve type for immutable u (SVector) + end +end + +# ============================================================================= +# Augmented RHS builder (variable-costate integration) +# ============================================================================= +# +# u = [x; p; pv] +# du/dt = [โˆ‚H/โˆ‚p; -โˆ‚H/โˆ‚x; -โˆ‚H/โˆ‚v] +# +# โš  Matrix batch mode is NOT supported: if x0 is a matrix each column would +# need its own pv, requiring a (nv, K) block in u. The current AD backends +# (hamiltonian_gradient, variable_gradient) are not defined for this layout. +# build_problem raises a NotImplemented error in that case. +# +# Batch-size consistency for the variable v (if it arrives as a matrix column) +# is checked at runtime via _check_aug_batch_compat. + +function _check_aug_batch_compat(u::AbstractMatrix, v::AbstractMatrix) + size(u, 2) == size(v, 2) && return nothing + throw(Exceptions.PreconditionError( + "batch size mismatch in augmented Hamiltonian RHS"; + reason = "size(u,2)=$(size(u,2)) โ‰  size(v,2)=$(size(v,2))", + context = "HamiltonianSystem.build_rhs_augmented โ€” matrix batch mode", + suggestion = "variable v must have the same number of columns as the state u", + )) +end +_check_aug_batch_compat(::Any, ::Any) = nothing # no-op for non-matrix cases + +""" + build_rhs_augmented(sys::HamiltonianSystem, n_x::Int, n_v::Int) -> f!(du,u,ฮป,t) + +Build the in-place RHS for the augmented Hamiltonian system +`u = [x; p; pv]` where `pv` is the variable costate. + +`n_x` and `n_v` are the dimensions of the state and variable respectively. +""" +function build_rhs_augmented(sys::HamiltonianSystem, n_x::Int, n_v::Int) + h, backend = sys.h, sys.backend + return function (du, u, ฮป, t) + v = Common.variable(ฮป) + _check_aug_batch_compat(u, v) + x, p, _ = _aug_split(u, n_x, n_v) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient( + backend, h, t, x, p, v, Common.cache(ฮป)) + โˆ‚pv = Differentiation.variable_gradient( + backend, h, t, x, p, v, Common.cache(ฮป)) + _aug_assign!(du, โˆ‚p, -โˆ‚x, -โˆ‚pv, n_x, n_v) + return nothing + end +end + +# ============================================================================= +# Legacy accessors (pre-built closures, used when x0/p0 are not available) +# ============================================================================= + +rhs(sys::HamiltonianSystem) = sys.rhs +rhs_oop(sys::HamiltonianSystem, ::Bool = true) = sys.rhs_oop + +# ============================================================================= +# build_problem (SciML integrator side) +# ============================================================================= + +# โ”€โ”€ Non-augmented โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function Integrators.build_problem( + integ ::SciML, + sys ::HamiltonianSystem, + config ::Common.AbstractHamiltonianConfig; + variable, + cache, +) + x0 = Common.initial_state(config) + p0 = Common.initial_costate(config) + u0 = Common.initial_condition(config) # = vcat(x0, p0) + ฮป = Common.ODEParameters(variable, cache) + + if ismutable(u0) + return ODEProblem(build_rhs(sys, x0, p0), u0, Common.tspan(config), ฮป) + else + return ODEProblem(build_oop_rhs(sys, x0, p0), u0, Common.tspan(config), ฮป) + end +end + +# โ”€โ”€ Augmented โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function Integrators.build_problem( + integ ::SciML, + sys ::HamiltonianSystem, + config ::Common.AbstractAugmentedHamiltonianConfig; + variable, + cache, +) + x0 = Common.initial_state(config) + u0 = Common.initial_condition(config) # = vcat(x0, p0, pv0) + ฮป = Common.ODEParameters(variable, cache) + + # Matrix batch mode is not supported for the augmented system. + if x0 isa AbstractMatrix + throw(Exceptions.NotImplemented( + "Augmented Hamiltonian flow with matrix initial condition"; + reason = "The variable costate pv layout in [x; p; pv] is ambiguous " * + "for batch (matrix) inputs: each column of x would need its " * + "own pv block, which is not supported by the current AD backends.", + suggestion = "Use a vector x0 and loop over initial conditions, or " * + "contact the CTFlows maintainers to discuss batch support.", + )) + end + + n_x = _state_dim(x0) + n_v = length(Common.initial_variable_costate(config)) + f! = build_rhs_augmented(sys, n_x, n_v) + return ODEProblem(f!, u0, Common.tspan(config), ฮป) +end + +# ============================================================================= +# Trait getter +# ============================================================================= + +function Traits.variable_costate_trait( + ::HamiltonianSystem{N, F, TD, Traits.NonFixed, B, R, O}) where {N, F, TD, B, R, O} + return Traits.SupportsVariableCostate +end + +# ============================================================================= +# Base.show +# ============================================================================= + +function Base.show(io::IO, sys::HamiltonianSystem{N, F, TD, VD, B}) where {N, F, TD, VD, B} + println(io, "HamiltonianSystem") + println(io, " state_dimension: ", N === nothing ? "unknown" : N) + println(io, " wraps: Hamiltonian: ", Data._td_label(TD), ", ", Data._vd_label(VD)) + print(io, " backend: ", B) +end + +Base.show(io::IO, ::MIME"text/plain", sys::HamiltonianSystem) = show(io, sys) diff --git a/reports/hamilonian/save/scalar_vector/hvfs_system.jl b/reports/hamilonian/save/scalar_vector/hvfs_system.jl new file mode 100644 index 00000000..41dfeed8 --- /dev/null +++ b/reports/hamilonian/save/scalar_vector/hvfs_system.jl @@ -0,0 +1,189 @@ +# ============================================================================= +# HamiltonianVectorFieldSystem +# ============================================================================= +# +# Design rationale +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# u = [x; p] requires a split whose shape depends on x0/p0. +# โ†’ RHS closures are built lazily in build_problem, not at construction. +# โ†’ Exactly ONE closure is created per flow call. +# โ†’ N is always an Int (never nothing); determined from x0 at call time. +# +# Shape coercion applies to the HVF *inputs* (x, p) only: +# Number โ†’ only(view) (1-element SubArray โ†’ scalar) +# AbstractVector โ†’ identity +# AbstractMatrix โ†’ identity +# +# HVF *outputs* (dx, dp): +# OOP HVF โ†’ returned as-is by hvf; assigned into du or vcat'd +# IP HVF โ†’ always mutable vector views (or allocated buffers), never coerced +# ============================================================================= + +struct HamiltonianVectorFieldSystem{ + F <: Function, + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, + MD <: Traits.AbstractMutabilityTrait, +} <: AbstractHamiltonianSystem{TD, VD, Traits.WithoutAD} + hvf::Data.HamiltonianVectorField{F, TD, VD, MD} +end + +# ============================================================================= +# IC-shape helpers +# ============================================================================= + +"""State dimension N inferred from the initial condition (always Int).""" +_state_dim(::Number) = 1 +_state_dim(x::AbstractVector) = length(x) +_state_dim(x::AbstractMatrix) = size(x, 1) + +""" +Closure that maps a vector view from `_ham_split` to the shape the HVF expects. +Applied to *inputs* (x, p) only โ€” never to output buffers. +""" +_make_coerce(::Number) = only # 1-element SubArray โ†’ scalar +_make_coerce(::AbstractVector) = identity +_make_coerce(::AbstractMatrix) = identity + +# ============================================================================= +# _ham_split / _ham_assign! +# N is always an Int here. +# ============================================================================= + +_ham_split(u::AbstractVector, N::Int) = (@view(u[1:N]), @view(u[N+1:2N])) +_ham_split(u::AbstractMatrix, N::Int) = (@view(u[1:N, :]), @view(u[N+1:2N, :])) + +_ham_assign!(du::AbstractVector, dx, dp, N::Int) = (du[1:N] .= dx; du[N+1:2N] .= dp) +_ham_assign!(du::AbstractMatrix, dx, dp, N::Int) = (du[1:N,:] .= dx; du[N+1:2N,:] .= dp) + +# ============================================================================= +# Internal closure builders +# ============================================================================= + +# โ”€โ”€ In-place closures (u0 mutable, e.g. Vector) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# OOP HVF: split โ†’ coerce inputs โ†’ call โ†’ assign back +function _build_ip_rhs_hvf(::Type{Traits.OutOfPlace}, hvf, N, cx, cp) + return function (du, u, ฮป, t) + xv, pv = _ham_split(u, N) + dx, dp = hvf(t, cx(xv), cp(pv), Common.variable(ฮป)) + _ham_assign!(du, dx, dp, N) + return nothing + end +end + +# IP HVF: split src and dst โ†’ coerce inputs โ†’ HVF writes into dst views +# dx_view / dp_view are NEVER coerced: they must remain mutable 1-D containers. +function _build_ip_rhs_hvf(::Type{Traits.InPlace}, hvf, N, cx, cp) + return function (du, u, ฮป, t) + xv, pv = _ham_split(u, N) + dxv, dpv = _ham_split(du, N) # mutable views โ†’ HVF writes into them + hvf(dxv, dpv, t, cx(xv), cp(pv), Common.variable(ฮป)) + return nothing + end +end + +# โ”€โ”€ Out-of-place closures (u0 immutable, e.g. SVector) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# OOP HVF: split โ†’ coerce inputs โ†’ call โ†’ vcat, convert back to typeof(u) +function _build_oop_rhs_hvf(::Type{Traits.OutOfPlace}, hvf, N, cx, cp) + return function (u, ฮป, t) + xv, pv = _ham_split(u, N) + dx, dp = hvf(t, cx(xv), cp(pv), Common.variable(ฮป)) + return typeof(u)(vcat(dx, dp)) + end +end + +# IP HVF + immutable u0 ("finalize" case): +# allocate mutable buffers โ†’ HVF writes โ†’ convert back to typeof(u). +# collect(view) ensures we get a plain Vector even if view is a SubArray. +function _build_oop_rhs_hvf(::Type{Traits.InPlace}, hvf, N, cx, cp) + return function (u, ฮป, t) + xv, pv = _ham_split(u, N) + dx, dp = similar(collect(xv)), similar(collect(pv)) + hvf(dx, dp, t, cx(xv), cp(pv), Common.variable(ฮป)) + return typeof(u)(vcat(dx, dp)) + end +end + +# ============================================================================= +# Public entry-points called by build_problem +# ============================================================================= + +""" + build_rhs(sys::HamiltonianVectorFieldSystem, x0, p0) -> f!(du,u,ฮป,t) + +Build the **in-place** RHS for `sys` specialised on the shapes of `x0` and `p0`. +Use when `u0 = vcat(x0, p0)` is mutable (e.g. `Vector`). + +Compiled once per `(typeof(x0), typeof(p0))` combination by Julia's dispatch. +""" +function build_rhs(sys::HamiltonianVectorFieldSystem{F,TD,VD,MD}, x0, p0) where {F,TD,VD,MD} + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + return _build_ip_rhs_hvf(MD, sys.hvf, N, cx, cp) +end + +""" + build_oop_rhs(sys::HamiltonianVectorFieldSystem, x0, p0) -> f(u,ฮป,t) + +Build the **out-of-place** RHS for `sys` specialised on the shapes of `x0` and `p0`. +Use when `u0 = vcat(x0, p0)` is immutable (e.g. `SVector`). + +For an IP HVF + immutable u0, mutable buffers are allocated internally and the +result is converted back to `typeof(u)`. A warning is emitted to encourage +switching to an OOP HVF in that case. +""" +function build_oop_rhs(sys::HamiltonianVectorFieldSystem{F,TD,VD,MD}, x0, p0) where {F,TD,VD,MD} + if MD === Traits.InPlace + @warn "InPlace HamiltonianVectorField with immutable u0 (e.g. SVector): " * + "prefer an out-of-place HVF for better performance." + end + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + return _build_oop_rhs_hvf(MD, sys.hvf, N, cx, cp) +end + +# ============================================================================= +# build_problem (SciML integrator side) +# ============================================================================= +# +# Decision tree: +# u0 mutable โ†’ build_rhs(sys, x0, p0) โ†’ ODEProblem(f!, u0, tspan, ฮป) +# u0 immutable โ†’ build_oop_rhs(sys, x0, p0) โ†’ ODEProblem(f, u0, tspan, ฮป) +# +# The HVF mutability (InPlace/OutOfPlace) is an implementation detail handled +# inside the builders; build_problem only sees the u0 mutability. + +function Integrators.build_problem( + integ ::SciML, + sys ::HamiltonianVectorFieldSystem, + config ::Common.AbstractConfig; + variable, + cache, +) + x0 = Common.initial_state(config) + p0 = Common.initial_costate(config) + u0 = Common.initial_condition(config) # = vcat(x0, p0) + ฮป = Common.ODEParameters(variable, cache) + + if ismutable(u0) + return ODEProblem(build_rhs(sys, x0, p0), u0, Common.tspan(config), ฮป) + else + return ODEProblem(build_oop_rhs(sys, x0, p0), u0, Common.tspan(config), ฮป) + end +end + +# ============================================================================= +# Base.show +# ============================================================================= + +function Base.show(io::IO, sys::HamiltonianVectorFieldSystem{F,TD,VD,MD}) where {F,TD,VD,MD} + println(io, "HamiltonianVectorFieldSystem") + print(io, " wraps: HamiltonianVectorField: ", + Data._td_label(TD), ", ", Data._vd_label(VD), ", ", Data._md_label(MD)) +end + +Base.show(io::IO, ::MIME"text/plain", sys::HamiltonianVectorFieldSystem) = show(io, sys) diff --git a/reports/hamilonian/save/scalar_vector/vfs_system.jl b/reports/hamilonian/save/scalar_vector/vfs_system.jl new file mode 100644 index 00000000..45623d71 --- /dev/null +++ b/reports/hamilonian/save/scalar_vector/vfs_system.jl @@ -0,0 +1,191 @@ +# ============================================================================= +# VectorFieldSystem +# ============================================================================= +# +# Design rationale +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# No split u โ†’ (x, p), no coerce: the RHS does not depend on the shape of x0. +# All three closures (in-place, oop, oop-finalize) are built once at construction. +# build_problem selects the single appropriate closure based on ismutable(u0). +# +# Shape consistency is natural: +# x0 :: Number โ†’ ismutable = false โ†’ oop path โ†’ vf(t,u,v) returns scalar โœ“ +# x0 :: SVector โ†’ ismutable = false โ†’ finalize path โ†’ typeof(u)(dx) โœ“ +# x0 :: Vector โ†’ ismutable = true โ†’ in-place path โœ“ +# +# Unsupported combination: InPlace VF + Number x0 (similar(::Number) fails). +# Detected at construction with a clear ArgumentError. +# ============================================================================= + +struct VectorFieldSystem{ + F <: Function, + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, + MD <: Traits.AbstractMutabilityTrait, + RHS <: Function, + OOPROHS <: Function, + FINRHS, # Function or Nothing +} <: AbstractStateSystem{TD, VD} + vf ::Data.VectorField{F, TD, VD, MD} + rhs ::RHS + rhs_oop ::OOPROHS + rhs_oop_finalize::FINRHS +end + +# ============================================================================= +# Constructors +# ============================================================================= + +function VectorFieldSystem(vf::Data.VectorField{F, TD, VD, Traits.OutOfPlace}) where {F, TD, VD} + rhs = _build_ip_rhs_vf(Traits.OutOfPlace, vf) + oop = _build_oop_rhs_vf(Traits.OutOfPlace, vf) + return VectorFieldSystem{F, TD, VD, Traits.OutOfPlace, + typeof(rhs), typeof(oop), Nothing}(vf, rhs, oop, nothing) +end + +function VectorFieldSystem(vf::Data.VectorField{F, TD, VD, Traits.InPlace}) where {F, TD, VD} + rhs = _build_ip_rhs_vf(Traits.InPlace, vf) + oop = _build_oop_rhs_vf(Traits.InPlace, vf) + fin = _build_finalize_rhs_vf(Traits.InPlace, vf) + return VectorFieldSystem{F, TD, VD, Traits.InPlace, + typeof(rhs), typeof(oop), typeof(fin)}(vf, rhs, oop, fin) +end + +# ============================================================================= +# Internal closure builders +# ============================================================================= + +# โ”€โ”€ In-place closures (u0 mutable, e.g. Vector) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# OOP VF: broadcast-assign the return value into du +function _build_ip_rhs_vf(::Type{Traits.OutOfPlace}, vf::Data.VectorField) + return (du, u, ฮป, t) -> (du .= vf(t, u, Common.variable(ฮป)); nothing) +end + +# IP VF: call vf directly with du as first argument +function _build_ip_rhs_vf(::Type{Traits.InPlace}, vf::Data.VectorField) + return (du, u, ฮป, t) -> (vf(du, t, u, Common.variable(ฮป)); nothing) +end + +# โ”€โ”€ Out-of-place closures (u0 immutable, e.g. SVector or Number) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# OOP VF: direct call, returns whatever vf returns (scalar, vector, โ€ฆ) +function _build_oop_rhs_vf(::Type{Traits.OutOfPlace}, vf::Data.VectorField) + return (u, ฮป, t) -> vf(t, u, Common.variable(ฮป)) +end + +# IP VF + mutable buffer: allocate similar(u), fill it, return it. +# Only reached when u0 is an AbstractArray (not a Number); see guard below. +function _build_oop_rhs_vf(::Type{Traits.InPlace}, vf::Data.VectorField) + return function (u, ฮป, t) + dx = similar(u) + vf(dx, t, u, Common.variable(ฮป)) + return dx + end +end + +# โ”€โ”€ Finalize closure (IP VF + immutable AbstractArray u0, e.g. SVector) โ”€โ”€โ”€โ”€โ”€โ”€ +# +# similar(u) returns a mutable counterpart (e.g. MVector for SVector). +# typeof(u)(dx) converts back to the original immutable type. +# Not defined for OutOfPlace VFs (rhs_oop_finalize = nothing). +function _build_finalize_rhs_vf(::Type{Traits.InPlace}, vf::Data.VectorField) + return function (u, ฮป, t) + dx = similar(u) + vf(dx, t, u, Common.variable(ฮป)) + return typeof(u)(dx) + end +end + +# ============================================================================= +# Guard: InPlace VF + scalar x0 is unsupported +# +# Called from build_problem before constructing the ODEProblem. +# A Number is immutable, so we would reach rhs_oop_finalize which calls +# similar(::Number) โ€” that errors. Better to fail early with a clear message. +# ============================================================================= + +function _check_vf_scalar_inplace( + sys::VectorFieldSystem{F, TD, VD, Traits.InPlace}, u0::Number) where {F, TD, VD} + throw(ArgumentError( + "An in-place VectorField cannot be used with a scalar initial condition " * + "(u0 :: $(typeof(u0))). Either use an out-of-place VectorField, or wrap " * + "the initial condition in a 1-element Vector: u0 = [$(u0)]." + )) +end +_check_vf_scalar_inplace(::VectorFieldSystem, ::Any) = nothing # no-op otherwise + +# ============================================================================= +# Public accessors +# ============================================================================= + +"""Return the pre-built in-place RHS `(du, u, ฮป, t) -> nothing`.""" +rhs(sys::VectorFieldSystem) = sys.rhs + +""" + rhs_oop(sys::VectorFieldSystem{โ€ฆ,OutOfPlace,โ€ฆ}, [::Bool]) -> Function + +Return the pre-built OOP RHS `(u, ฮป, t) -> du`. +The Bool argument is accepted for API uniformity but ignored. +""" +function rhs_oop(sys::VectorFieldSystem{F, TD, VD, Traits.OutOfPlace, RHS, OOPROHS, Nothing}, + ::Bool = true) where {F, TD, VD, RHS, OOPROHS} + return sys.rhs_oop +end + +""" + rhs_oop(sys::VectorFieldSystem{โ€ฆ,InPlace,โ€ฆ}, is_u0_mutable::Bool) -> Function + +- `is_u0_mutable = true` โ†’ `rhs_oop` (allocates mutable buffer, returns it) +- `is_u0_mutable = false` โ†’ `rhs_oop_finalize` (allocates, converts back to `typeof(u)`) + +A warning is emitted for the immutable case to encourage switching to an OOP VF. +""" +function rhs_oop(sys::VectorFieldSystem{F, TD, VD, Traits.InPlace, RHS, OOPROHS, FINRHS}, + is_u0_mutable::Bool = true) where {F, TD, VD, RHS, OOPROHS, FINRHS} + is_u0_mutable && return sys.rhs_oop + @warn "InPlace VectorField with immutable u0 (e.g. SVector): " * + "prefer an out-of-place VectorField for better performance." + return sys.rhs_oop_finalize +end + +# ============================================================================= +# build_problem (SciML integrator side) +# ============================================================================= +# +# Decision: +# u0 mutable โ†’ f!(du,u,ฮป,t) via rhs(sys) +# u0 immutable โ†’ f(u,ฮป,t) via rhs_oop(sys, false) +# +# The VF mutability (InPlace/OutOfPlace) is transparent to the caller here; +# it was baked into the closures at construction time. + +function Integrators.build_problem( + integ ::SciML, + sys ::VectorFieldSystem, + config ::Common.AbstractConfig; + variable, + cache, +) + u0 = Common.initial_condition(config) + _check_vf_scalar_inplace(sys, u0) # guard: IP VF + Number โ†’ error + ฮป = Common.ODEParameters(variable, cache) + + if ismutable(u0) + return ODEProblem(rhs(sys), u0, Common.tspan(config), ฮป) + else + return ODEProblem(rhs_oop(sys, false), u0, Common.tspan(config), ฮป) + end +end + +# ============================================================================= +# Base.show +# ============================================================================= + +function Base.show(io::IO, sys::VectorFieldSystem{F, TD, VD, MD}) where {F, TD, VD, MD} + println(io, "VectorFieldSystem") + print(io, " wraps: VectorField: ", + Data._td_label(TD), ", ", Data._vd_label(VD), ", ", Data._md_label(MD)) +end + +Base.show(io::IO, ::MIME"text/plain", sys::VectorFieldSystem) = show(io, sys) diff --git a/reports/notes.md b/reports/notes.md new file mode 100644 index 00000000..41c2ceba --- /dev/null +++ b/reports/notes.md @@ -0,0 +1,7 @@ +# Notes + +- **Getter for Hamiltonian vector field (HVF) from Hamiltonian, system, and flow**: Implement a unified getter mechanism to extract the Hamiltonian vector field from different contexts (Hamiltonian data structure, system definition, or flow object). This would provide a consistent interface for accessing the vector field regardless of the source. + +- **Replace closures with callable structs**: Refactor the codebase to use callable structs (functors) instead of closures. This improves type stability, performance, and debuggability. Callable structs can store state explicitly and are more amenable to compilation and optimization by the Julia compiler. + +- **Improve consistency between scalar and vector operations**: Review the use of `scalarize` in solution construction to ensure proper handling of scalar vs vector cases. Determine if the current scalarization approach is semantically correct or if it indicates a deeper inconsistency in the API design. This may involve unifying the interface to handle both cases more uniformly. \ No newline at end of file diff --git a/reports/roadmap.md b/reports/roadmap.md new file mode 100644 index 00000000..e10682b5 --- /dev/null +++ b/reports/roadmap.md @@ -0,0 +1,118 @@ +# Roadmap + +A major refactoring is planned. The simplest approach is to start from scratch, setting aside the existing code and tests, and then rebuild everything following the roadmap below. + +## API and Accessors + +- **Getters for `H` and `\vec{H}`**: provide accessors to retrieve the Hamiltonian $H$ and the Hamiltonian vector field $\vec{H}$ from a flow. See [issue #185](https://github.com/control-toolbox/CTFlows.jl/issues/185). +- **Mandatory named variable argument**: for a flow built on an OCP with a variable, the variable must be supplied via a keyword argument when calling the flow. See [issue #183](https://github.com/control-toolbox/CTFlows.jl/issues/183). +- **Input validation at flow call**: at the beginning of a flow call, check that user-provided functions (e.g. the control) define the expected methods on the intended arguments. This should verify the autonomous vs. non-autonomous and variable vs. non-variable cases. + +## Integrators and Solvers + +- **Pluggable integrator backends**: the flow should be usable by simply loading a basic integrator such as `OrdinaryDiffEqTsit5.jl`. `Tsit5` will be the default solver, and the solver should be switchable via a keyword argument together with an additional `using`. +- **GPU support**: ensure the flow works on GPU, and add dedicated tests. + +## Flow Construction + +- **Multi-phase flows for concatenation**: to concatenate flows, introduce a multi-phase flow concept. The concatenation is restricted to flows built from the same OCP. **Note**: Multi-phase design is deferred to a later phase. The current v1 design focuses on single-phase flows with abstract types and contract testing. Multi-phase flows were described in the [v0 design document](./v0/design.md) and will require additional types (e.g., `MultiPhaseSystem`, `MultiPhaseFlow`) and concatenation logic. The v1 design does not yet include multi-phase support. +- **Closed- and open-loop encapsulation**: support flows built by encapsulating `DynClosedLoop`, `ClosedLoop`, and `OpenLoop`. See [issue #134](https://github.com/control-toolbox/CTFlows.jl/issues/134), [discussion #144](https://github.com/control-toolbox/CTFlows.jl/discussions/144), and the [review document](https://github.com/control-toolbox/CTFlows.jl/blob/134-dev-add-flow-for-open-closed-loop-control/review.md). +- **Flows on generic objects**: consider whether to support creating a flow directly from a `Hamiltonian`, an `ODEProblem`, or an `ODEFunction`. +- **Flow on a control-free OCP**: keep the ability to build a flow on an OCP that has no control (a `ControlFreeModel`). See `ext/optimal_control_problem.jl`. +- **Augmented flow**: keep the notion of augmented Hamiltonian flow, i.e. the system extended with the costate equation $dp_v/dt = -\partial H/\partial v$ for the dual variable associated with the OCP variable $v$. See `rhs_augmented` in `ext/hamiltonian.jl`. +- **Implicit control and DAE**: support implicit control formulations and DAE systems. See [issue #46](https://github.com/control-toolbox/CTFlows.jl/issues/46). + +## Outputs + +- **Return the dual variable**: when available, the flow should return the dual variable. See [issue #103](https://github.com/control-toolbox/CTFlows.jl/issues/103). +- **Correct handling of the flow derivative**: See [issue #93](https://github.com/control-toolbox/CTFlows.jl/issues/93). + +## Differential Geometry + +- **Introduce `ad`**: restructure the differential geometry code by introducing `ad`, which will replace work already started. Care is required because other modifications have already been made in `differential_geometry.jl`. The code should probably be organized as a module with several files, following the structure of [`CTSolvers.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/CTSolvers.jl) and its submodules. Relevant references: + - [Differential geometry guide (v0.9.0)](https://github.com/control-toolbox/CTFlows.jl/blob/release/v0.9.0/docs/src/differential-geometry-guide.md) + - [`differential_geometry.jl` (v0.9.0)](https://github.com/control-toolbox/CTFlows.jl/blob/release/v0.9.0/src/differential_geometry.jl) + - [`test_differential_geometry.jl` (v0.9.0)](https://github.com/control-toolbox/CTFlows.jl/blob/release/v0.9.0/test/test_differential_geometry.jl) + - [`utils.jl` (v0.9.0)](https://github.com/control-toolbox/CTFlows.jl/blob/release/v0.9.0/src/utils.jl) +- **`Lift` must return a function**: the `Lift` of a function must itself return a function. See [issue #99](https://github.com/control-toolbox/CTFlows.jl/issues/99). +- **Exception handling in `@Lie`**: ensure proper exception handling inside the `@Lie` macro. See [issue #94](https://github.com/control-toolbox/CTFlows.jl/issues/94). + +## Strategies Architecture + +- **Strategy families**: create strategy families (in the CTSolvers sense) and strategies for modeling and solving a flow or a system. This will require defining a contract for strategy families. References: + - [Implementing a strategy](https://control-toolbox.org/CTSolvers.jl/stable/guides/implementing_a_strategy.html) + - [Strategy parameters](https://control-toolbox.org/CTSolvers.jl/stable/guides/strategy_parameters.html) + - [Implementing a solver](https://control-toolbox.org/CTSolvers.jl/stable/guides/implementing_a_solver.html) + - [Implementing a modeler](https://control-toolbox.org/CTSolvers.jl/stable/guides/implementing_a_modeler.html) +- **High-level vs. atomic API**: either provide a single `solve` method, or follow the CTSolvers approach with atomic methods that amount to calling a strategy on the object itself (e.g. solving an NLP with a solver, see [`common_solve_api.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Solvers/common_solve_api.jl)). The same pattern can be applied to retrieving a model or discretizing a problem. There is always a functional level and an object level: at the functional level, when several strategies must be combined, a high-level "recipe" is exposed, which progressively drops down to atomic calls. See [high-level API](https://github.com/control-toolbox/CTSolvers.jl/blob/ff6e57d3dd598a4143b8f2bf0a85d5fa4c264c92/src/Solvers/common_solve_api.jl#L45-L62) and [low-level API](https://github.com/control-toolbox/CTSolvers.jl/blob/ff6e57d3dd598a4143b8f2bf0a85d5fa4c264c92/src/Solvers/common_solve_api.jl#L87-L91). + +## Tests and Documentation + +- **Revamp tests and documentation** following the CTBase guides: + - [Test runner guide](https://control-toolbox.org/CTBase.jl/stable/guide/test-runner.html) + - [API documentation guide](https://control-toolbox.org/CTBase.jl/stable/guide/api-documentation.html) + +--- + +April 29th, 2026 + +Actually, we need only one strategy family which will be `AbstractIntegrator`. We will use it to model and solve flows. When calling a flow, we have two possibilities: + +- `xf[, pf] = f(t0, x0[, p0], tf)` +- `sol = f((t0, tf), x0[, p0])` + +where `sol` is a solution object that contains the trajectory and possibly the costate as functions of time. We will call `config` either `(t0, x0[, p0], tf)` or `((t0, tf), x0[, p0])`. We could imagine having a struct representing these two different config to dispatch the calls. + +So, we will have: + +```julia +function solve(system, config, integrator) + f = build_flow(system, integrator) + return f(config) +end +``` + +where `integrator` is a strategy, `(system, config)` is the object and `solve` is the action, according to the pattern: + +```julia +action(object, strategies...) โ†’ result +``` + +- The **object** is the thing being acted upon โ€” a problem, a system, a model. +- The **strategies** are the configured descriptors that control *how* the action is carried out. +- The **result** is a new object: a solution, a flow, a trajectory, โ€ฆ + +We will have: + +```julia +function (flow::AbstractFlow)(config) + r = integrate(system(flow), config, integrator(flow)) + s = build_solution(r, flow, config) + return s +end +``` + +To build a system from a VectorField, a Hamiltonian, an OCP with a control law, etc, we will have a function `build_system` that takes the object and returns a system. + +```julia +system = build_system(object) +``` + +When needed, we will have to pass the AD backend to `build_system`: + +```julia +system = build_system(object, ad_backend) +``` + +but it is not always needed. For instance, for a VectorField, the AD backend is not needed. Here, there is no strategy involved. + +So we need: + +- `build_system(object)` for objects that do not need an AD backend +- `build_system(object, ad_backend)` for objects that need an AD backend +- we need an abstract type for the system +- we need an abstract type for the flow +- we need an abstract type for the solution. We have also CTModels.Solution for a solution of an optimal control problem. +- we need to define the family `AbstractIntegrator` for the integrator strategy and concrete implementations like `SciMLIntegrator`. +- `integrate(system, config, integrator)` is the action that integrates the system with the given configuration and integrator. +- `build_solution(result, flow, config)` is the action that builds a solution from the result of the integration. \ No newline at end of file diff --git a/reports/save/SciMLBase/scimlbase_plan.md b/reports/save/SciMLBase/scimlbase_plan.md new file mode 100644 index 00000000..5e813876 --- /dev/null +++ b/reports/save/SciMLBase/scimlbase_plan.md @@ -0,0 +1,654 @@ +# CTFlowsSciMLBase Extension โ€” Implementation Plan + +## Context + +CTFlows.jl currently supports flow construction from `VectorField` and +`HamiltonianVectorField` (CTFlows native types). Users working directly with the +SciML ecosystem โ€” using `SciMLBase.AbstractODEFunction` or +`SciMLBase.AbstractODEProblem` โ€” have no direct entry point into the CTFlows +integration pipeline. + +The existing extension `CTFlowsSciML = ["DiffEqBase", "SciMLBase"]` handles the +SciML backend (ODE solving, segment merging, `real_norm`). It requires **both** +`DiffEqBase` and `SciMLBase` and must not be modified for this feature. + +This plan introduces a new, lighter extension `CTFlowsSciMLBase = ["SciMLBase"]` +that depends **only on `SciMLBase`** and provides: + +1. `SciMLFunctionSystem` โ€” wraps a `SciMLBase.AbstractODEFunction` as a CTFlows system. +2. `SciMLProblemFlow` โ€” wraps a `SciMLBase.AbstractODEProblem` as a CTFlows flow. +3. `Flow(f::AbstractODEFunction; ...)` โ€” high-level constructor. +4. `Flow(prob::AbstractODEProblem; ...)` โ€” high-level constructor. + +## Design Decisions + +### Trait assignment for SciML types + +`AbstractODEFunction{iip}` encodes mutability in its type parameter: +- `iip = true` โ†’ in-place `f!(du, u, p, t)` โ†’ maps to `InPlace` +- `iip = false` โ†’ out-of-place `f(u, p, t)` โ†’ maps to `OutOfPlace` + +Both `SciMLFunctionSystem` and `SciMLProblemFlow` use `NonAutonomous, NonFixed` +as their TD/VD traits โ€” the most general case, since SciML functions always +accept `(u, p, t)` or `(du, u, p, t)` and `p` is user-controlled. + +### `p` is passed as `variable` directly, without `ODEParameters` + +The CTFlows-native systems use `ODEParameters(variable, cache)` to thread the +variable through the ODE. SciML-native systems bypass this: `p = variable` +directly, so users can pass arbitrary SciML parameter objects. This requires a +dedicated `build_problem` overload for `SciMLFunctionSystem`. + +### `SciMLProblemFlow` does not wrap an `AbstractSystem` + +A `SciMLBase.AbstractODEProblem` is already fully assembled (u0, tspan, p +baked in). There is no `AbstractSystem` to extract. `SciMLProblemFlow` inherits +directly from `AbstractFlow{NonAutonomous, NonFixed}` and exposes two call modes: +- **No-arg call** `f()` โ€” solve the problem as-is. +- **Remake call** `f(t0, x0, tf; variable)` โ€” call `SciMLBase.remake` first. + +### Separation from `CTFlowsSciML` + +`CTFlowsSciML` defines `solve_problem` for `SciMLBase.AbstractODEProblem` +generically. To avoid ambiguity when both extensions are loaded, the +`SciMLBaseIntegrationResult` type is defined here and `solve_problem` in +`CTFlowsSciMLBase` dispatches on it, while `CTFlowsSciML` dispatches on +`SciMLIntegrationResult`. The dispatch tree is disjoint. + +### `system(f::SciMLProblemFlow)` contract + +`AbstractFlow` requires `system` and `integrator`. For `SciMLProblemFlow`, +`system` returns `nothing` (there is no CTFlows system), and `integrator` returns +the integrator. The `Base.show` method handles this gracefully. + +--- + +## Dependency Graph + +``` +Common (NonAutonomous, NonFixed, AbstractConfig, __unsafe) + โ†“ +Systems (AbstractStateSystem) + โ†“ +Integrators (AbstractIntegrator, SciML, build_integrator, + AbstractIntegrationResult, build_problem, solve_problem) + โ†“ +Flows (AbstractFlow, AbstractStateFlow, build_flow, StateFlow) + โ†“ +ext/CTFlowsSciMLBase (SciMLFunctionSystem, SciMLProblemFlow, + SciMLBaseIntegrationResult, Flow overloads) +``` + +--- + +## Phase 1 โ€” Project Configuration + +### Step 1 โ€” `Project.toml` (modified) + +Add `CTFlowsSciMLBase` as a new weak dependency extension triggered by +`SciMLBase` alone: + +```toml +[extensions] +CTFlowsForwardDiff = ["ForwardDiff"] +CTFlowsOrdinaryDiffEqTsit5 = ["OrdinaryDiffEqTsit5"] +CTFlowsPlots = ["Plots"] +CTFlowsSciML = ["DiffEqBase", "SciMLBase"] +CTFlowsSciMLBase = ["SciMLBase"] # new +CTFlowsStaticArrays = ["StaticArrays"] +``` + +`SciMLBase` is already in `[weakdeps]` โ€” no new dependency to declare. + +### Step 2 โ€” Test Checkpoint: extension loading + +- `@testset "Unit: CTFlowsSciMLBase loads without DiffEqBase"` โ€” load only + `SciMLBase` and verify the extension activates. +- `@testset "Unit: CTFlowsSciML still loads with DiffEqBase + SciMLBase"` โ€” + verify no conflict with the existing extension. + +--- + +## Phase 2 โ€” `SciMLFunctionSystem` + +### Step 3 โ€” `ext/CTFlowsSciMLBase.jl` (new file) โ€” skeleton + +```julia +module CTFlowsSciMLBase + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using CTFlows: CTFlows +using CTFlows.Common: Common +using CTFlows.Systems: Systems +using CTFlows.Integrators: Integrators, SciML +using CTFlows.Flows: Flows, AbstractFlow, AbstractStateFlow, build_flow +using CTFlows.Solutions: Solutions +using SciMLBase: SciMLBase + +# ... (filled in subsequent steps) + +end # module CTFlowsSciMLBase +``` + +### Step 4 โ€” `SciMLFunctionSystem` struct and accessors + +Define the system type. TD and VD are fixed at `NonAutonomous, NonFixed` because +SciML functions always receive `(u, p, t)` and `p` is arbitrary. + +```julia +""" +$(TYPEDEF) + +Concrete `AbstractStateSystem` wrapping a `SciMLBase.AbstractODEFunction`. + +Unlike CTFlows-native systems (`VectorFieldSystem`), this system passes `p = variable` +directly to the ODE โ€” no `ODEParameters` wrapper โ€” so users can pass arbitrary +SciML parameter objects. + +The mutability trait is encoded in the `iip` type parameter of the wrapped function: +- `AbstractODEFunction{true}` โ†’ in-place `f!(du, u, p, t)` +- `AbstractODEFunction{false}` โ†’ out-of-place `f(u, p, t) -> du` + +# Type Parameters +- `F <: SciMLBase.AbstractODEFunction`: The wrapped ODE function. + +# Fields +- `f::F`: The wrapped SciML ODE function. +""" +struct SciMLFunctionSystem{ + F <: SciMLBase.AbstractODEFunction +} <: Systems.AbstractStateSystem{Common.NonAutonomous, Common.NonFixed} + f::F +end +``` + +Add `rhs` and `rhs_oop` dispatching on `iip`: + +```julia +# In-place: rhs returns f directly (already has (du, u, p, t) signature) +Systems.rhs(sys::SciMLFunctionSystem{<:SciMLBase.AbstractODEFunction{true}}) = sys.f + +# Out-of-place: rhs_oop returns f directly (already has (u, p, t) -> du signature) +# Bool argument accepted for API uniformity, ignored +Systems.rhs_oop( + sys::SciMLFunctionSystem{<:SciMLBase.AbstractODEFunction{false}}, + ::Bool = true, +) = sys.f +``` + +### Step 5 โ€” `build_problem` overload for `SciMLFunctionSystem` + +Bypasses `ODEParameters`: passes `variable` directly as `p`. + +```julia +function Integrators.build_problem( + integ::SciML, + sys::SciMLFunctionSystem, + config::Common.AbstractConfig; + variable, +) + u0 = Common.initial_condition(config) + tspan = Common.tspan(config) + if ismutable(u0) + prob = SciMLBase.ODEProblem{true}(sys.f, u0, tspan, variable) + else + prob = SciMLBase.ODEProblem{false}(sys.f, u0, tspan, variable) + end + return prob +end +``` + +### Step 6 โ€” `SciMLBaseIntegrationResult` + +A minimal integration result type that does not depend on `DiffEqBase`, keeping +this extension independent of the heavier `CTFlowsSciML`. + +```julia +""" +$(TYPEDEF) + +Integration result wrapping a `SciMLBase.AbstractODESolution`. + +Defined in `CTFlowsSciMLBase` (depends only on `SciMLBase`, not `DiffEqBase`), +parallel to `SciMLIntegrationResult` in `CTFlowsSciML`. +""" +struct SciMLBaseIntegrationResult{ + S <: SciMLBase.AbstractODESolution +} <: Integrators.AbstractIntegrationResult + ode_sol::S +end + +Integrators.final_state(r::SciMLBaseIntegrationResult) = last(r.ode_sol.u) +Integrators.times(r::SciMLBaseIntegrationResult) = r.ode_sol.t +Integrators.evaluate_at(r::SciMLBaseIntegrationResult, t) = r.ode_sol(t) +``` + +### Step 7 โ€” `solve_problem` for `SciMLFunctionSystem` path + +Dispatches on `SciMLBaseIntegrationResult` to stay disjoint from `CTFlowsSciML`: + +```julia +function Integrators.solve_problem( + integ::SciML, + prob::SciMLBase.AbstractODEProblem, + options::Dict{Symbol, <:Any}; + unsafe = Common.__unsafe(), +) + sol = SciMLBase.solve(prob; options...) + _check_retcode(sol, unsafe) + return SciMLBaseIntegrationResult(sol) +end +``` + +### Step 8 โ€” `Base.show` for `SciMLFunctionSystem` + +```julia +function Base.show(io::IO, sys::SciMLFunctionSystem{F}) where F + iip = SciMLBase.isinplace(sys.f) + mut = iip ? "in-place" : "out-of-place" + println(io, "SciMLFunctionSystem") + print(io, " wraps: ODEFunction: non-autonomous, variable, ", mut) +end + +function Base.show(io::IO, ::MIME"text/plain", sys::SciMLFunctionSystem) + show(io, sys) +end +``` + +### Step 9 โ€” Test Checkpoint: `SciMLFunctionSystem` + +File: `test/suite/extensions/test_scimlbase_function_system.jl` + +- `@testset "Unit: SciMLFunctionSystem from iip ODEFunction"` โ€” struct construction, + `Systems.rhs` returns the function directly +- `@testset "Unit: SciMLFunctionSystem from oop ODEFunction"` โ€” struct construction, + `Systems.rhs_oop` returns the function directly +- `@testset "Unit: ad_trait is WithoutAD"` โ€” verify trait +- `@testset "Unit: build_problem passes variable as p directly"` โ€” inspect `prob.p` +- `@testset "Unit: Base.show"` โ€” smoke test on display +- `@testset "Integration: StateFlow from SciMLFunctionSystem"` โ€” build_flow, call + with `variable=2.0`, verify `prob.p == 2.0` during integration +- `@testset "Integration: iip vs oop give same result"` โ€” numerical equivalence + +--- + +## Phase 3 โ€” `SciMLProblemFlow` + +### Step 10 โ€” `SciMLProblemFlow` struct + +Inherits directly from `AbstractFlow{NonAutonomous, NonFixed}` โ€” not from +`AbstractStateFlow` since there is no `AbstractStateSystem` to parametrize. + +```julia +""" +$(TYPEDEF) + +Concrete flow wrapping a `SciMLBase.AbstractODEProblem` directly. + +Unlike CTFlows-native flows (`StateFlow`, `HamiltonianFlow`), this flow does not +wrap a `AbstractSystem`. The ODE problem is already fully assembled (u0, tspan, p). + +Two call modes are supported: +- **No-arg** `f()`: solves the problem as-is, returns the raw `ODESolution`. +- **Remake** `f(t0, x0, tf; variable)`: calls `SciMLBase.remake` then solves, + returns `xf` (final state only). + +# Type Parameters +- `P <: SciMLBase.AbstractODEProblem`: The wrapped ODE problem. +- `I <: Integrators.AbstractIntegrator`: The integrator strategy. + +# Fields +- `prob::P`: The wrapped SciML ODE problem. +- `integrator::I`: The integrator strategy. +""" +struct SciMLProblemFlow{ + P <: SciMLBase.AbstractODEProblem, + I <: Integrators.AbstractIntegrator, +} <: AbstractFlow{Common.NonAutonomous, Common.NonFixed} + prob::P + integrator::I +end +``` + +### Step 11 โ€” `AbstractFlow` contract methods for `SciMLProblemFlow` + +```julia +# No underlying CTFlows system โ€” return nothing +Flows.system(f::SciMLProblemFlow) = nothing +Flows.integrator(f::SciMLProblemFlow) = f.integrator +``` + +### Step 12 โ€” Call signatures for `SciMLProblemFlow` + +**No-arg call** โ€” solve as-is, return raw `SciMLBase.AbstractODESolution`: + +```julia +""" +$(TYPEDSIGNATURES) + +Solve the wrapped ODE problem as-is. + +Returns the raw `SciMLBase.AbstractODESolution` โ€” the full trajectory, accessible +via `sol(t)`, `sol.u`, `sol.t`. + +# Example +```julia +prob = ODEProblem((du, u, p, t) -> du .= -u, [1.0], (0.0, 1.0)) +f = Flow(prob) +sol = f() +``` +""" +function (f::SciMLProblemFlow)(; unsafe = Common.__unsafe()) + opts = Integrators.build_options(f.integrator, nothing) + sol = SciMLBase.solve(f.prob; opts...) + _check_retcode(sol, unsafe) + return sol +end +``` + +**Remake call** โ€” new `u0`, `tspan`, optional `p`; returns final state `xf`: + +```julia +""" +$(TYPEDSIGNATURES) + +Solve with new initial condition and time span via `SciMLBase.remake`. + +The variable `p` parameter is passed directly as the ODE `p` (no `ODEParameters` +wrapper). Returns the final state `xf = u(tf)`. + +# Arguments +- `t0::Real`: New initial time. +- `x0`: New initial state. +- `tf::Real`: New final time. +- `variable`: New parameter passed as `p` to the ODE (default: `nothing`). +- `unsafe`: If `true`, bypass retcode checking. + +# Returns +- Final state vector `xf`. + +# Example +```julia +prob = ODEProblem((du, u, p, t) -> du .= -p .* u, [1.0], (0.0, 1.0), 1.0) +f = Flow(prob) +xf = f(0.0, [2.0], 1.0) # remake u0 and tspan +xf = f(0.0, [2.0], 1.0; variable=3.0) # also override p +``` +""" +function (f::SciMLProblemFlow)( + t0::Real, + x0, + tf::Real; + variable = nothing, + unsafe = Common.__unsafe(), +) + # Build remake kwargs: always update u0 and tspan, update p only if variable given + kw = (; u0=x0, tspan=(t0, tf)) + variable !== nothing && (kw = merge(kw, (; p=variable))) + prob = SciMLBase.remake(f.prob; kw...) + opts = Integrators.build_options( + f.integrator, + Common.StatePointConfig(t0, x0, tf), + ) + sol = SciMLBase.solve(prob; opts...) + _check_retcode(sol, unsafe) + return last(sol.u) +end +``` + +### Step 13 โ€” `Base.show` for `SciMLProblemFlow` + +```julia +function Base.show(io::IO, ::MIME"text/plain", f::SciMLProblemFlow) + println(io, "SciMLProblemFlow") + println(io, " prob: ODEProblem (tspan=$(f.prob.tspan), u0=$(f.prob.u0))") + print(io, " integrator: ", nameof(typeof(f.integrator))) + Flows._print_user_options(io, f.integrator) +end + +function Base.show(io::IO, f::SciMLProblemFlow) + print(io, "SciMLProblemFlow(tspan=", f.prob.tspan, ")") +end +``` + +### Step 14 โ€” Test Checkpoint: `SciMLProblemFlow` + +File: `test/suite/extensions/test_scimlbase_problem_flow.jl` + +- `@testset "Unit: SciMLProblemFlow construction"` โ€” from iip and oop problems +- `@testset "Unit: system(f) returns nothing"` +- `@testset "Unit: integrator(f) returns integrator"` +- `@testset "Unit: Base.show"` โ€” smoke test +- `@testset "Integration: no-arg call returns ODESolution"` โ€” `sol.u`, `sol.t` accessible +- `@testset "Integration: remake call returns xf"` โ€” scalar and vector states +- `@testset "Integration: remake with variable overrides p"` โ€” verify `prob.p` in remake +- `@testset "Integration: unsafe=true skips retcode check"` +- `@testset "Error: failed solve with unsafe=false"` โ€” `SolverFailure` thrown + +--- + +## Phase 4 โ€” High-Level `Flow` Constructors + +### Step 15 โ€” `Flow(f::AbstractODEFunction; ...)` constructor + +```julia +""" +$(TYPEDSIGNATURES) + +Build a `StateFlow` from a `SciMLBase.AbstractODEFunction`. + +Wraps the function in a `SciMLFunctionSystem` and builds a standard CTFlows +`StateFlow`. The `variable` keyword at call time is passed directly as `p` +to the ODE โ€” no `ODEParameters` wrapper. + +The mutability trait (in-place vs out-of-place) is inferred from the `iip` +type parameter of the function. + +# Arguments +- `f::SciMLBase.AbstractODEFunction`: The ODE function to wrap. +- `opts...`: Keyword options forwarded to `build_integrator` (e.g., `reltol`, `abstol`, `alg`). + +# Returns +- `StateFlow{NonAutonomous, NonFixed, SciMLFunctionSystem, SciML}`. + +# Example +```julia +using SciMLBase, CTFlows + +# In-place +fip = ODEFunction((du, u, p, t) -> du .= -p .* u) +flow = Flow(fip; reltol=1e-10) +xf = flow(0.0, [1.0], 1.0; variable=2.0) + +# Out-of-place +foop = ODEFunction{false}((u, p, t) -> -p .* u) +flow = Flow(foop) +xf = flow(0.0, [1.0], 1.0; variable=2.0) +``` +""" +function CTFlows.Flows.Flow(f::SciMLBase.AbstractODEFunction; opts...) + sys = SciMLFunctionSystem(f) + integ = Integrators.build_integrator(; opts...) + return build_flow(sys, integ) +end +``` + +### Step 16 โ€” `Flow(prob::AbstractODEProblem; ...)` constructor + +```julia +""" +$(TYPEDSIGNATURES) + +Build a `SciMLProblemFlow` from a `SciMLBase.AbstractODEProblem`. + +The problem is stored as-is. Two call modes are available: +- `f()` โ€” solve as-is, returns the raw `ODESolution`. +- `f(t0, x0, tf; variable)` โ€” remake then solve, returns `xf`. + +# Arguments +- `prob::SciMLBase.AbstractODEProblem`: The ODE problem to wrap. +- `opts...`: Keyword options forwarded to `build_integrator`. + +# Returns +- `SciMLProblemFlow`. + +# Example +```julia +using SciMLBase, CTFlows, OrdinaryDiffEqTsit5 + +prob = ODEProblem((du, u, p, t) -> du .= -u, [1.0], (0.0, 1.0)) +f = Flow(prob) + +sol = f() # solve as-is +xf = f(0.0, [2.0], 2.0) # remake and solve +``` +""" +function CTFlows.Flows.Flow(prob::SciMLBase.AbstractODEProblem; opts...) + integ = Integrators.build_integrator(; opts...) + return SciMLProblemFlow(prob, integ) +end +``` + +### Step 17 โ€” Test Checkpoint: High-level constructors + +File: `test/suite/extensions/test_scimlbase_flow_constructors.jl` + +- `@testset "Unit: Flow(ODEFunction) builds StateFlow"` โ€” type check +- `@testset "Unit: Flow(ODEFunction) iip"` โ€” rhs inferred correctly +- `@testset "Unit: Flow(ODEFunction) oop"` โ€” rhs_oop inferred correctly +- `@testset "Unit: Flow(ODEProblem) builds SciMLProblemFlow"` โ€” type check +- `@testset "Integration: Flow(ODEFunction) end-to-end"` โ€” numerical result +- `@testset "Integration: Flow(ODEProblem) no-arg call"` โ€” sol accessible +- `@testset "Integration: Flow(ODEProblem) remake call"` โ€” xf correct +- `@testset "Integration: Flow(ODEFunction) with opts"` โ€” reltol, abstol respected +- `@testset "Integration: Flow(ODEProblem) with opts"` โ€” same + +--- + +## Phase 5 โ€” Conflict Prevention with `CTFlowsSciML` + +### Step 18 โ€” Verify dispatch disjointness + +When both `CTFlowsSciML` and `CTFlowsSciMLBase` are loaded simultaneously +(i.e., `DiffEqBase`, `SciMLBase` both present), two `solve_problem` methods +exist for `SciMLBase.AbstractODEProblem`. They must be disjoint: + +- `CTFlowsSciML.solve_problem` โ†’ returns `SciMLIntegrationResult` +- `CTFlowsSciMLBase.solve_problem` โ†’ returns `SciMLBaseIntegrationResult` + +Since Julia dispatches on argument types, both methods have the same signature +`(SciML, AbstractODEProblem, Dict; unsafe)` โ€” **this is ambiguous**. + +**Resolution**: restrict `CTFlowsSciMLBase.solve_problem` to only be called via +`SciMLFunctionSystem`'s `build_problem` path by making it internal (not exported, +only reachable through the `StateFlow` call pipeline via `build_problem` dispatch +on `SciMLFunctionSystem`). The `build_problem` overload for `SciMLFunctionSystem` +constructs an `ODEProblem` tagged via a wrapper: + +```julia +# Thin wrapper to disambiguate dispatch at solve time +struct SciMLBaseODEProblem{P <: SciMLBase.AbstractODEProblem} + prob::P +end + +function Integrators.build_problem(integ::SciML, sys::SciMLFunctionSystem, config; variable) + u0 = Common.initial_condition(config) + prob = SciMLBase.ODEProblem(sys.f, u0, Common.tspan(config), variable) + return SciMLBaseODEProblem(prob) # wrapped โ†’ unambiguous dispatch +end + +function Integrators.solve_problem( + integ::SciML, + prob::SciMLBaseODEProblem, + options::Dict{Symbol, <:Any}; + unsafe = Common.__unsafe(), +) + sol = SciMLBase.solve(prob.prob; options...) + _check_retcode(sol, unsafe) + return SciMLBaseIntegrationResult(sol) +end +``` + +`CTFlowsSciML.solve_problem` dispatches on the raw `SciMLBase.AbstractODEProblem` +โ€” the two methods are now on disjoint types. + +### Step 19 โ€” Test Checkpoint: coexistence + +File: `test/suite/extensions/test_scimlbase_coexistence.jl` + +- `@testset "Integration: CTFlowsSciML and CTFlowsSciMLBase coexist"` โ€” load both + DiffEqBase and SciMLBase, verify both paths work without method ambiguity +- `@testset "Integration: VectorField flow still works with both extensions"` โ€” + regression +- `@testset "Integration: ODEFunction flow correct when DiffEqBase loaded"` โ€” + `SciMLBaseIntegrationResult` used, not `SciMLIntegrationResult` + +--- + +## Phase 6 โ€” Documentation + +### Step 20 โ€” Docstrings + +Write docstrings for: +- `SciMLFunctionSystem` struct, `rhs`, `rhs_oop`, `build_problem`, `Base.show` +- `SciMLBaseIntegrationResult` struct, `final_state`, `times`, `evaluate_at` +- `SciMLProblemFlow` struct, both call signatures, `Base.show` +- `Flow(::AbstractODEFunction; ...)`, `Flow(::AbstractODEProblem; ...)` +- `SciMLBaseODEProblem` (internal wrapper, brief note) + +### Step 21 โ€” Final Test Run + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/scimlbase.log +grep -E "Error|Fail|Test Summary" /tmp/scimlbase.log +``` + +Expected: all suites pass, zero failures, zero errors. + +--- + +## Files Summary + +### New +- `ext/CTFlowsSciMLBase.jl` +- `test/suite/extensions/test_scimlbase_function_system.jl` +- `test/suite/extensions/test_scimlbase_problem_flow.jl` +- `test/suite/extensions/test_scimlbase_flow_constructors.jl` +- `test/suite/extensions/test_scimlbase_coexistence.jl` + +### Modified +- `Project.toml` โ€” add `CTFlowsSciMLBase = ["SciMLBase"]` to `[extensions]` + +### Deleted +None. + +--- + +## User-Facing API Summary + +```julia +using CTFlows, SciMLBase, OrdinaryDiffEqTsit5 + +# โ”€โ”€ From AbstractODEFunction โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# In-place +fip = ODEFunction((du, u, p, t) -> du .= -p .* u) +flow = Flow(fip; reltol=1e-10) +xf = flow(0.0, [1.0], 1.0) # p = nothing +xf = flow(0.0, [1.0], 1.0; variable=2.0) # p = 2.0 + +# Out-of-place +foop = ODEFunction{false}((u, p, t) -> -p .* u) +flow = Flow(foop) +xf = flow(0.0, [1.0], 1.0; variable=2.0) + +# โ”€โ”€ From AbstractODEProblem โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +prob = ODEProblem((du, u, p, t) -> du .= -u, [1.0], (0.0, 1.0)) +flow = Flow(prob; abstol=1e-10) + +sol = flow() # solve as-is โ†’ ODESolution +xf = flow(0.0, [2.0], 1.0) # remake u0, tspan โ†’ xf +xf = flow(0.0, [2.0], 1.0; variable=3.0) # also override p โ†’ xf +``` diff --git a/reports/save/complex_and_static_arrays_support.md b/reports/save/complex_and_static_arrays_support.md new file mode 100644 index 00000000..923a1430 --- /dev/null +++ b/reports/save/complex_and_static_arrays_support.md @@ -0,0 +1,179 @@ +# Complex Number and Static Arrays Support Report + +**Date**: 2026-05-11 +**Status**: Completed + +## Overview + +This report documents the addition of comprehensive complex number support for VectorField and HamiltonianVectorField systems, along with improvements to the CTFlowsStaticArrays extension to support SMatrix and handle both known and inferred state dimensions. + +## Changes Made + +### 1. Complex Number Support + +#### Problem +The `deepvalue` and `real_norm` functions in `src/Common/internal_norm.jl` were restricted to `Real` numbers, causing `MethodError` when using complex states in SciML integration tests. + +#### Solution +Extended both functions to support `Number` (which includes both `Real` and `Complex`): + +```julia +deepvalue(x::Number) = x +real_norm(u::Number, t) = abs(u) +``` + +This is type-stable because `ForwardDiff.Dual <: Real` remains more specific and takes dispatch priority. + +#### Files Modified +- `src/Common/internal_norm.jl` โ€” Added `Number` methods for `deepvalue` and `real_norm` +- Updated docstrings to reflect complex support + +### 2. Complex Number Tests + +#### Unit Tests +Added unit tests for complex scalar, vector, and matrix inputs in both systems: + +**VectorFieldSystem** (`test/suite/systems/test_vector_field_system.jl`): +- Complex vector `rhs` and `rhs_oop` tests +- Complex matrix `rhs` and `rhs_oop` tests +- SVector complex tests + +**HamiltonianVectorFieldSystem** (`test/suite/systems/test_hamiltonian_vector_field_system.jl`): +- Complex vector `rhs` and `rhs_oop` tests +- Complex matrix `rhs` and `rhs_oop` tests +- SVector complex tests + +#### Integration Tests +Added integration tests for complex initial states in SciML flows: + +**StateFlow** (`test/suite/extensions/test_flow_callables_sciml.jl`): +- Scalar complex initial condition +- Vector complex initial condition +- Matrix complex initial condition +- SVector complex initial condition + +**HamiltonianFlow** (`test/suite/extensions/test_flow_callables_sciml.jl`): +- Scalar complex (x0, p0) +- Vector complex (x0, p0) +- Matrix complex (x0, p0) +- SVector complex (x0, p0) + +#### Type Check Improvements +Updated type checks to be more specific: +- `isa Real` for real-valued tests +- `isa Complex` for complex-valued tests +- Replaced generic `isa Number` where appropriate + +### 3. CTFlowsStaticArrays Extension Improvements + +#### Problem +The extension only supported `SVector` with known state dimension `N`, but: +- No support for `SMatrix` +- No support for inferred dimension (`::Nothing`) +- Type parameters not extracted at compile time + +#### Solution + +**Type Parameter Extraction** +Changed signatures to extract type parameters at compile time: + +```julia +function _ham_split(u::StaticVector{NN, T}, N::Int) where {NN, T} +function _ham_split(u::StaticVector{NN, T}, ::Nothing) where {NN, T} +function _ham_split(u::StaticMatrix{NN, M, T}, N::Int) where {NN, M, T} +function _ham_split(u::StaticMatrix{NN, M, T}, ::Nothing) where {NN, M, T} +``` + +This allows Julia to infer `M` and `T` from the type parameters, eliminating runtime calls to `size(u)`. + +**SMatrix Support** +Added `_ham_split` for `StaticMatrix` with column-major linear indexing: + +```julia +X = SMatrix{N, M, T}(ntuple(k -> u[(k-1) % N + 1, (k-1) รท N + 1], Val(N*M))) +P = SMatrix{N, M, T}(ntuple(k -> u[N + (k-1) % N + 1, (k-1) รท N + 1], Val(N*M))) +``` + +The `ntuple((j,i) -> ...)` syntax is invalid โ€” must use single index with column-major encoding. + +#### Files Modified +- `ext/CTFlowsStaticArrays.jl` โ€” Added SMatrix support and ::Nothing variants +- Updated module docstring to reflect SMatrix support +- Updated method docstrings + +### 4. Test Coverage + +#### Unit Tests for Static Dispatch +Added comprehensive tests for all 4 dispatch cases of `_ham_split`: + +**SVector:** +- SVector + N known +- SVector + N nothing (inferred as NN รท 2) + +**SMatrix:** +- SMatrix + N known +- SMatrix + N nothing (inferred as NN รท 2) + +#### Integration Tests for Static Arrays +Added integration tests for SVector and SMatrix with both known and inferred dimensions: + +**SVector:** +- SVector x0, p0 (N known) โ€” uses HSYS_N2 +- SVector x0, p0 (N nothing) โ€” uses HSYS_NO_N +- SVector complex x0, p0 (N known) + +**SMatrix:** +- SMatrix x0, p0 (N known) +- SMatrix x0, p0 (N nothing) + +**Important Discovery**: SMatrix integration tests show that while `vcat(SMatrix, SMatrix)` returns an `SMatrix`, the ODE solver (Tsit5) converts it to a regular `Matrix` internally. Therefore, the output is always `AbstractMatrix`, not `SMatrix`. This is a limitation of the ODE solver, not the CTFlows architecture. + +## Test Summary + +### Current Test Count +- `test_flow_callables_sciml.jl`: 74 tests +- `test_hamiltonian_vector_field_system.jl`: 67 tests + +### Supported Configurations + +| System | State Type | Real | Complex | Unit Tests | Integration Tests | +|---------|------------|------|----------|------------|-------------------| +| VectorField | Scalar | โœ… | โœ… | โœ… | โœ… | +| VectorField | Vector | โœ… | โœ… | โœ… | โœ… | +| VectorField | Matrix | โœ… | โœ… | โœ… | โœ… | +| VectorField | SVector | โœ… | โœ… | โœ… | โœ… | +| HamiltonianFlow | Scalar | โœ… | โœ… | โœ… | โœ… | +| HamiltonianFlow | Vector | โœ… | โœ… | โœ… | โœ… | +| HamiltonianFlow | Matrix | โœ… | โœ… | โœ… | โœ… | +| HamiltonianFlow | SVector | โœ… | โœ… | โœ… | โœ… | +| HamiltonianFlow | SMatrix | โœ… | โŒ | โœ… | โŒ | + +**Note on SMatrix**: Unit tests for `_ham_split` work correctly. Integration tests show the ODE solver does not preserve SMatrix types (returns `AbstractMatrix`). This is a limitation of SciML/OrdinaryDiffEq, not CTFlows architecture. + +## Architecture Decisions + +### No Override of `initial_condition` +Considered but rejected for the following reasons: + +1. **SVector works as-is**: `vcat(SVector{N}, SVector{N})` returns `SVector{2N}` via StaticArrays' implementation of `Base.vcat`. No override needed. + +2. **SMatrix limitation is downstream**: Even if `initial_condition` returned an `SMatrix`, the ODE solver would convert it to `Matrix` internally. Overriding `initial_condition` would not solve the problem. + +3. **Helper `_ham_join` not needed**: The current `vcat(c.x0, c.p0)` is already correct for all types. Adding a helper would only be useful if we wanted to transform SMatrix into a flattened SVector{2NM}, which is a non-trivial transformation with unclear benefit. + +### Type Parameter Inference +Using `StaticVector{NN, T}` and `StaticMatrix{NN, M, T}` in method signatures allows Julia to extract type parameters at compile time, making the code fully type-stable without runtime dimension queries. + +## Limitations + +1. **SMatrix with ODE Solvers**: SciML's Tsit5 solver does not preserve SMatrix types. Initial conditions are converted to Matrix internally, and the output is always `AbstractMatrix`. This is a limitation of the ODE solver ecosystem, not CTFlows. + +2. **No scalar complex HamiltonianFlow with matrix costate**: Not tested (edge case, unlikely use case). + +## Recommendations + +1. **Document SMatrix limitation**: Add a note in the CTFlowsStaticArrays extension docstring explaining that SMatrix types are preserved during RHS computation but not through ODE integration. + +2. **Monitor SciML updates**: Future versions of SciML may add better SMatrix support. If this happens, the architecture is ready to take advantage of it with no changes needed. + +3. **Consider SVector output types**: Currently, even SVector outputs are converted to `Vector` by the ODE solver. If SciML adds better static array support, we may want to add integration tests that verify SVector preservation. diff --git a/reports/save/concatenation/analysis.md b/reports/save/concatenation/analysis.md new file mode 100644 index 00000000..89ff37d9 --- /dev/null +++ b/reports/save/concatenation/analysis.md @@ -0,0 +1,929 @@ +# Concatรฉnation de Flots โ€” Analyse et Proposition + +## Rรฉsumรฉ Exรฉcutif + +Ce document analyse les diffรฉrentes approches de concatรฉnation de flots et propose une architecture adaptรฉe ร  CTFlows v1. La proposition repose sur : + +- une **intรฉgration sรฉquentielle exacte** au niveau des flots (pas des systรจmes) +- une **hiรฉrarchie de types** distinguant les flots รฉtat-seul (`AbstractStateFlow`) des flots hamiltoniens (`AbstractHamiltonianFlow`) +- une **contrainte compile-time** garantissant que seuls des flots de mรชme type de systรจme peuvent รชtre concatรฉnรฉs +- une **fusion progressive** des rรฉsultats pour minimiser la mรฉmoire utilisรฉe + +--- + +## 1. Ancienne Approche (save/ext/concatenation.jl + ext_utils.jl) + +### Principe + +Concatรฉnation "ร  la volรฉe" pendant l'intรฉgration continue : on construit un nouveau `rhs!` qui dispatche selon `t < t_switch`, puis on intรจgre le tout en une seule passe ODE. + +### API + +```julia +F * (t_switch, G) # F jusqu'ร  t_switch, puis G, sans saut +F * (t_switch, ฮท, G) # avec saut ฮท ร  t_switch +``` + +### Implรฉmentation Interne + +```julia +function __concat_rhs(F, G, t_switch) + return (du, u, p, t) -> t < t_switch ? F.rhs!(du, u, p, t) : G.rhs!(du, u, p, t) +end + +function __concat_tstops(F, G, t_switch) + tstops = [F.tstops; G.tstops; t_switch] + return unique(sort(tstops)) +end +``` + +### Gestion des Sauts (Callbacks SciML) + +Les sauts d'รฉtat รฉtaient gรฉrรฉs via `VectorContinuousCallback` : + +```julia +function condition(out, u, t, integrator) + out[:] = t_jumps .- t # Zรฉro-crossing aux temps de saut +end + +function affect!(integrator, event_index) + integrator.u += ฮท_jumps[event_index] # Appliquer le saut +end + +cbjumps = VectorContinuousCallback(condition, affect!, length(jumps)) +cb = CallbackSet(cbjumps, user_callback) +``` + +Les `tstops` (temps de switching inclus) รฉtaient ensuite passรฉs ร  l'intรฉgrateur pour forcer une รฉvaluation exacte aux discontinuitรฉs. + +### Problรจme Identifiรฉ : Non-ร‰quivalence avec la Concatรฉnation Exacte + +OptimalControl.jl a documentรฉ le mรชme problรจme avec leur approche similaire : + +```julia +# Deux faรงons de calculer "f de t0 ร  t/2 puis g de t/2 ร  t" +ฯ†(t) = (f * (t/2, g))(0, x0, t) # approche "ร  la volรฉe" : une seule intรฉgration +ฯˆ(t) = g(t/2, f(0, x0, t/2), t) # approche exacte : deux intรฉgrations sรฉquentielles + +# Rรฉsultat : ฯ†(t) โ‰  ฯˆ(t) +# L'erreur |ฯ†(t) - ฯˆ(t)| croรฎt avec t +``` + +**Cause profonde** : Mรชme avec `tstops`, le solveur ne dรฉmarre pas une nouvelle intรฉgration ร  `t_switch`. Il continue l'intรฉgration courante avec un nouveau `rhs!`. Les รฉtats internes du solveur (step-size history, Jacobian approchรฉ, etc.) ne sont pas rรฉinitialisรฉs. La prรฉcision locale autour de `t_switch` est donc dรฉgradรฉe. + +--- + +## 2. Approche OptimalControl.jl + +### Principe + +Mรชme opรฉrateur `*`, mรชme mรฉcanisme interne (intรฉgration continue avec `rhs!` qui switch). + +```julia +f0 = Flow(ocp, (x, p) -> 0) # off arc : u = 0 +f1 = Flow(ocp, (x, p) -> 1) # positive bang arc : u = 1 +f = f0 * (t1, f1) # concatรฉnation +sol = f((t0, tf), x0, p0) +``` + +### Limitation Documentรฉe + +> "For the moment, this concatenation is not equivalent to an exact concatenation." +> +> โ€” Documentation OptimalControl.jl + +Le graphique de leur exemple montre des erreurs numรฉriques croissant avec `t`, confirmant la non-รฉquivalence. + +--- + +## 3. Ancienne Rรฉflexion (Discussion GitHub #144) โ€” Intรฉgration Sรฉquentielle + +### Principe + +Intรฉgrer chaque phase sรฉparรฉment, puis fusionner les rรฉsultats. La concatรฉnation se fait au **niveau des systรจmes** (`MultiPhaseSystem`). + +### Architecture Proposรฉe + +```julia +struct PhaseTransition + time::Float64 + jump::Tuple{Vector, Vector} # (ฮ”x, ฮ”p) +end + +struct MultiPhaseSystem{S<:AbstractSystem} <: AbstractSystem + phases::Vector{S} + transitions::Vector{PhaseTransition} +end +``` + +### Flux pour ร‰valuation Point + +```julia +function evaluate_point(mps::MultiPhaseSystem, t0, z0, tf, v) + phase_idx = find_phase(mps, t0) + z_current = z0 + t_current = t0 + + for k in phase_idx:length(mps.phases) + t_end = k < length(mps.phases) ? mps.transitions[k].time : tf + t_end = min(t_end, tf) + + problem = _ode_problem(mps.phases[k], (t_current, t_end), z_current, v) + raw_sol = _solve(problem, get_alg(mps), get_options(mps)) + + z_current = raw_sol[end] + + if t_end < tf && k < length(mps.phases) + ฮ”x, ฮ”p = mps.transitions[k].jump + z_current = apply_jump(z_current, ฮ”x, ฮ”p) + end + + t_current = t_end + t_current >= tf && break + end + + return split_state_costate(z_current) +end +``` + +### Flux pour Solution Complรจte + +```julia +function evaluate_solution(mps::MultiPhaseSystem, tspan, z0, v) + solutions = [] + z_current = z0 + t_current = tspan[1] + + for k in 1:length(mps.phases) + t_end = k < length(mps.phases) ? mps.transitions[k].time : tspan[2] + t_end = min(t_end, tspan[2]) + + phase_sol = _solve(mps.phases[k], (t_current, t_end), z_current) + push!(solutions, phase_sol) + + if t_end < tspan[2] && k < length(mps.phases) + z_current = phase_sol[end] + ฮ”x, ฮ”p = mps.transitions[k].jump + z_current = apply_jump(z_current, ฮ”x, ฮ”p) + end + + t_current = t_end + t_current >= tspan[2] && break + end + + return _merge_solutions(solutions, mps) +end +``` + +### Fusion des Solutions (Extension SciML) + +```julia +function _merge_solutions(solutions::Vector, mps::MultiPhaseSystem) + u = [uc for (k, sol) in enumerate(solutions) for uc in sol.u[1:end-1]] + t = [tc for (k, sol) in enumerate(solutions) for tc in sol.t[1:end-1]] + + push!(u, solutions[end].u[end]) + push!(t, solutions[end].t[end]) + + return SciMLBase.build_solution(solutions[1].prob, solutions[1].alg, t, u; retcode=:Success) +end +``` + +### Limitations + +- Concatรฉnation au niveau des **systรจmes**, pas des **flots** (contraire ร  l'usage utilisateur) +- Chaque phase partage le mรชme intรฉgrateur (celui du `MultiPhaseSystem`) +- Types de sauts limitรฉs aux vecteurs + +--- + +## 4. Proposition pour CTFlows v1 + +### Principe Central + +**L'utilisateur construit des flots et concatรจne des flots.** Il ne manipule pas directement les systรจmes. La concatรฉnation produit un nouveau flot qui s'utilise exactement comme un flot simple. + +```julia +# Construction +f1 = Flow(sys, integrator_opts) +f2 = Flow(sys, integrator_opts) + +# Concatรฉnation : produit un MultiPhaseStateFlow ou MultiPhaseHamiltonianFlow +f = f1 * (t_switch, f2) + +# Usage identique ร  un flot simple +xf = f(t0, x0, tf) # StatePointConfig +sol = f((t0, tf), x0) # StateTrajectoryConfig +``` + +La concatรฉnation est **associative** et peut รชtre chaรฎnรฉe : + +```julia +f = f1 * (t1, f2) * (t2, f3) * (t3, f4) # 4 phases +``` + +### Contrainte de Type + +**Seuls des flots de mรชme type de systรจme peuvent รชtre concatรฉnรฉs.** Cette contrainte est vรฉrifiรฉe ร  la **compilation** par le paramรจtre de type `S` dans les sous-types d'`AbstractFlow`, pas ร  l'exรฉcution. + +```julia +# OK : mรชme type de systรจme (VectorFieldSystem) +f1 = Flow(VectorFieldSystem(vf1), opts) +f2 = Flow(VectorFieldSystem(vf2), opts) +f = f1 * (1.0, f2) # โœ“ MultiPhaseStateFlow + +# ERREUR ร  la compilation : types S diffรฉrents +f1 = Flow(VectorFieldSystem(vf), opts) +f2 = Flow(HamiltonianSystem(H), opts) +f = f1 * (1.0, f2) # โœ— MethodError : aucune mรฉthode * ne correspond +``` + +### Hiรฉrarchie de Types + +#### AbstractFlow โ€” type parent + +`AbstractFlow` reste identique ร  aujourd'hui (sans paramรจtre de systรจme) : + +```julia +abstract type AbstractFlow{TD<:Common.TimeDependence, VD<:Common.VariableDependence} end +``` + +#### Sous-types abstraits โ€” portent le paramรจtre S + +```julia +# Flots sur l'espace d'รฉtat seul (x) +# - VectorField, Hamiltonian rรฉduit, etc. +# - Les sauts ne concernent que ฮ”x +abstract type AbstractStateFlow{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + S <: AbstractSystem{TD, VD} +} <: AbstractFlow{TD, VD} end + +# Flots sur l'espace รฉtendu (x, p) โ€” systรจmes hamiltoniens +# - Les sauts peuvent porter sur ฮ”p seul, ou (ฮ”x, ฮ”p) +abstract type AbstractHamiltonianFlow{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + S <: AbstractSystem{TD, VD} +} <: AbstractFlow{TD, VD} end +``` + +`AbstractStateFlow` et `AbstractHamiltonianFlow` ne sont pas reliรฉs entre eux : on ne peut pas concatรฉner un flot de chaque sous-type. + +#### Flow concret โ€” rattachรฉ ร  un sous-type + +Le type `Flow` existant devient un sous-type de l'un ou l'autre selon le systรจme : + +```julia +struct Flow{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + S <: AbstractSystem{TD, VD}, + I <: Integrators.AbstractIntegrator +} <: AbstractStateFlow{TD, VD, S} # ou AbstractHamiltonianFlow selon S + sys :: S + int :: I +end +``` + +### Sรฉmantique des Sauts + +#### AbstractStateFlow + +Un seul saut possible : `ฮ”x` (de n'importe quel type supportant `+`). + +```julia +f1 * (t_switch, f2) # pas de saut +f1 * (t_switch, ฮ”x, f2) # saut ฮ”x sur l'รฉtat +``` + +`ฮ”x` peut รชtre : +- un scalaire (`Float64`, `Int`, nombre dual) +- un vecteur +- une matrice +- tout type pour lequel `+` est dรฉfini avec l'รฉtat + +#### AbstractHamiltonianFlow + +L'espace d'รฉtat est `(x, p)`. Convention : **si un seul saut est fourni, c'est ฮ”p** (le cas le plus courant en contrรดle optimal). + +```julia +f1 * (t_switch, f2) # pas de saut +f1 * (t_switch, ฮ”p, f2) # saut ฮ”p seul sur le costate +f1 * (t_switch, ฮ”x, ฮ”p, f2) # sauts ฮ”x et ฮ”p +``` + +### Types MultiPhase + +#### MultiPhaseStateFlow + +```julia +struct MultiPhaseStateFlow{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + F <: AbstractStateFlow{TD, VD, S}, + S <: AbstractSystem{TD, VD} +} <: AbstractStateFlow{TD, VD, S} + phases :: Vector{F} + switching_times :: Vector{<:Real} # temps absolus, longueur = length(phases) - 1 + jumps :: Vector{Union{Nothing, Any}} # ฮ”x ou nothing, mรชme longueur +end +``` + +Invariant : `length(switching_times) == length(jumps) == length(phases) - 1` + +#### MultiPhaseHamiltonianFlow + +```julia +struct MultiPhaseHamiltonianFlow{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + F <: AbstractHamiltonianFlow{TD, VD, S}, + S <: AbstractSystem{TD, VD} +} <: AbstractHamiltonianFlow{TD, VD, S} + phases :: Vector{F} + switching_times :: Vector{<:Real} + jumps :: Vector{Union{Nothing, Tuple{Union{Nothing,Any}, Union{Nothing,Any}}}} + # jumps[k] = nothing โ†’ pas de saut ร  la transition k + # jumps[k] = (nothing, ฮ”p) โ†’ saut ฮ”p seul + # jumps[k] = (ฮ”x, nothing) โ†’ saut ฮ”x seul + # jumps[k] = (ฮ”x, ฮ”p) โ†’ sauts ฮ”x et ฮ”p +end +``` + +### Opรฉrateurs de Concatรฉnation + +#### Pour AbstractStateFlow + +```julia +# Sans saut +function Base.:*( + f1 :: AbstractStateFlow{TD, VD, S}, + g :: Tuple{<:Real, <:AbstractStateFlow{TD, VD, S}} +) where {TD, VD, S} + t_switch, f2 = g + # Propager : si f1 est dรฉjร  un MultiPhaseStateFlow, รฉtendre les phases + phases = _flatten_phases(f1, f2) + switching_times = _flatten_times(f1, t_switch, f2) + jumps = _flatten_jumps(f1, nothing, f2) + return MultiPhaseStateFlow(phases, switching_times, jumps) +end + +# Avec saut ฮ”x +function Base.:*( + f1 :: AbstractStateFlow{TD, VD, S}, + g :: Tuple{<:Real, Any, <:AbstractStateFlow{TD, VD, S}} +) where {TD, VD, S} + t_switch, ฮ”x, f2 = g + phases = _flatten_phases(f1, f2) + switching_times = _flatten_times(f1, t_switch, f2) + jumps = _flatten_jumps(f1, ฮ”x, f2) + return MultiPhaseStateFlow(phases, switching_times, jumps) +end +``` + +#### Pour AbstractHamiltonianFlow + +```julia +# Sans saut +function Base.:*( + f1 :: AbstractHamiltonianFlow{TD, VD, S}, + g :: Tuple{<:Real, <:AbstractHamiltonianFlow{TD, VD, S}} +) where {TD, VD, S} + t_switch, f2 = g + return MultiPhaseHamiltonianFlow( + _flatten_phases(f1, f2), + _flatten_times(f1, t_switch, f2), + _flatten_jumps(f1, nothing, f2) + ) +end + +# Saut ฮ”p seul (convention : un seul saut = ฮ”p) +function Base.:*( + f1 :: AbstractHamiltonianFlow{TD, VD, S}, + g :: Tuple{<:Real, Any, <:AbstractHamiltonianFlow{TD, VD, S}} +) where {TD, VD, S} + t_switch, ฮ”p, f2 = g + return MultiPhaseHamiltonianFlow( + _flatten_phases(f1, f2), + _flatten_times(f1, t_switch, f2), + _flatten_jumps(f1, (nothing, ฮ”p), f2) + ) +end + +# Sauts ฮ”x et ฮ”p +function Base.:*( + f1 :: AbstractHamiltonianFlow{TD, VD, S}, + g :: Tuple{<:Real, Any, Any, <:AbstractHamiltonianFlow{TD, VD, S}} +) where {TD, VD, S} + t_switch, ฮ”x, ฮ”p, f2 = g + return MultiPhaseHamiltonianFlow( + _flatten_phases(f1, f2), + _flatten_times(f1, t_switch, f2), + _flatten_jumps(f1, (ฮ”x, ฮ”p), f2) + ) +end +``` + +#### Aplatissement (chaรฎnage associatif) + +Pour que `f1 * (t1, f2) * (t2, f3)` produise un `MultiPhaseStateFlow` ร  3 phases (et non un `MultiPhaseStateFlow` contenant un autre `MultiPhaseStateFlow`) : + +```julia +# Si f1 est dรฉjร  un MultiPhaseStateFlow, extraire ses phases +function _flatten_phases(f1::MultiPhaseStateFlow, f2::AbstractStateFlow) + return [f1.phases; f2] +end +function _flatten_phases(f1::AbstractStateFlow, f2::AbstractStateFlow) + return [f1, f2] +end + +function _flatten_times(f1::MultiPhaseStateFlow, t_switch, f2) + return [f1.switching_times; t_switch] +end +function _flatten_times(f1::AbstractStateFlow, t_switch, f2) + return [t_switch] +end + +function _flatten_jumps(f1::MultiPhaseStateFlow, jump, f2) + return [f1.jumps; jump] +end +function _flatten_jumps(f1::AbstractStateFlow, jump, f2) + return [jump] +end +# Idem pour AbstractHamiltonianFlow / MultiPhaseHamiltonianFlow +``` + +### Intรฉgration Sรฉquentielle dans call() + +Deux mรฉthodes distinctes selon `StatePointConfig` (pas de fusion) et `StateTrajectoryConfig` (fusion progressive). + +#### StatePointConfig โ€” pas de stockage intermรฉdiaire + +```julia +function call( + flow :: Union{MultiPhaseStateFlow, MultiPhaseHamiltonianFlow}, + config :: Common.StatePointConfig; + variable, unsafe +) + t0, tf = Common.tspan(config) + z_current = Common.initial_condition(config) + t_current = t0 + + for (k, phase_flow) in enumerate(flow.phases) + t_end = k < length(flow.phases) ? flow.switching_times[k] : tf + t_end = min(t_end, tf) + + phase_config = _make_phase_config(t_current, t_end, z_current, config) + phase_result = call(phase_flow, phase_config; variable, unsafe) + z_current = _extract_final_state(phase_result) + + # Appliquer saut si prรฉsent (uniquement entre deux phases, pas aprรจs la derniรจre) + if k < length(flow.phases) && !isnothing(flow.jumps[k]) + z_current = _apply_jump(flow, z_current, flow.jumps[k]) + end + + t_current = t_end + t_current >= tf && break + end + + return z_current +end +``` + +#### StateTrajectoryConfig โ€” fusion progressive en mรฉmoire O(N_points) + +```julia +function call( + flow :: Union{MultiPhaseStateFlow, MultiPhaseHamiltonianFlow}, + config :: Common.StateTrajectoryConfig; + variable, unsafe +) + t0, tf = Common.tspan(config) + z_current = Common.initial_condition(config) + t_current = t0 + + # Vecteurs accumulateurs : une seule copie des donnรฉes + t_all = eltype(flow.switching_times)[] + u_all = typeof(z_current)[] + + for (k, phase_flow) in enumerate(flow.phases) + t_end = k < length(flow.phases) ? flow.switching_times[k] : tf + t_end = min(t_end, tf) + + phase_config = _make_phase_config(t_current, t_end, z_current, config) + phase_result = call(phase_flow, phase_config; variable, unsafe) + + # Inclure tous les points sauf le dernier des phases intermรฉdiaires + # (le premier point de la phase suivante coรฏncide avec le dernier de celle-ci) + n_pts = k < length(flow.phases) && t_end < tf ? length(phase_result.t) - 1 : length(phase_result.t) + append!(t_all, phase_result.t[1:n_pts]) + append!(u_all, phase_result.u[1:n_pts]) + + z_current = _extract_final_state(phase_result) + + if k < length(flow.phases) && !isnothing(flow.jumps[k]) + z_current = _apply_jump(flow, z_current, flow.jumps[k]) + end + + t_current = t_end + t_current >= tf && break + end + + return _build_merged_solution(t_all, u_all, flow) +end +``` + +### Fonctions Auxiliaires + +#### _make_phase_config + +Construit une configuration pour une phase individuelle. Le type de config est prรฉservรฉ. + +```julia +function _make_phase_config(t_start, t_end, z0, ::Common.StatePointConfig) + return Common.StatePointConfig(t_start, z0, t_end) +end + +function _make_phase_config(t_start, t_end, z0, ::Common.StateTrajectoryConfig) + return Common.StateTrajectoryConfig((t_start, t_end), z0) +end +``` + +#### _extract_final_state + +Extrait l'รฉtat final du rรฉsultat d'une phase. +Pour `StatePointConfig`, le rรฉsultat **est** dรฉjร  l'รฉtat final. +Pour `StateTrajectoryConfig`, l'รฉtat final est le dernier point de la trajectoire. + +```julia +_extract_final_state(result::AbstractVector) = result # StatePointConfig โ†’ dรฉjร  l'รฉtat +_extract_final_state(result::VectorFieldSolution) = result.u[end] # StateTrajectoryConfig +``` + +#### _apply_jump + +Dispatch sur le type de flot pour appliquer le saut correctement. + +```julia +# Pour MultiPhaseStateFlow : saut ฮ”x direct (scalaire, vecteur, dual, etc.) +function _apply_jump(::MultiPhaseStateFlow, z, ฮ”x) + return z + ฮ”x +end + +# Pour MultiPhaseHamiltonianFlow : saut (ฮ”x, ฮ”p) avec composantes optionnelles +function _apply_jump(::MultiPhaseHamiltonianFlow, z::Tuple, jump::Tuple) + ฮ”x, ฮ”p = jump + x_new = isnothing(ฮ”x) ? z[1] : z[1] + ฮ”x + p_new = isnothing(ฮ”p) ? z[2] : z[2] + ฮ”p + return (x_new, p_new) +end +``` + +#### _build_merged_solution + +Dรฉlรฉguรฉ ร  l'extension `CTFlowsSciML`, qui construit un `ODESolution` ร  partir des vecteurs accumulรฉs. + +```julia +# Dans src/ : stub dรฉlรฉguรฉ ร  l'extension +function _build_merged_solution(t, u, flow) + throw(Exceptions.ExtensionError( + "CTFlowsSciML is required to build merged solutions"; + extension = "CTFlowsSciML", + )) +end + +# Dans ext/CTFlowsSciML.jl : implรฉmentation rรฉelle +function CTFlows._build_merged_solution(t, u, flow) + # On utilise le prob/alg de la premiรจre phase comme template + first_phase = flow.phases[1] + prob = Integrators.last_prob(first_phase) # prob SciML de la derniรจre intรฉgration + alg = Integrators.last_alg(first_phase) + return SciMLBase.build_solution(prob, alg, t, u; retcode=:Success) +end +``` + +### Contrat AbstractFlow pour les Types Multi-phase + +Les types multi-phase doivent implรฉmenter le contrat `AbstractFlow`. Les mรฉthodes `system()` et `integrator()` n'ont pas de rรฉponse unique (il y a N systรจmes et N intรฉgrateurs). Trois options sont possibles. + +#### Option A โ€” Convention "premiรจre phase" (simple, discutable) + +```julia +function Flows.system(flow::MultiPhaseStateFlow) + return Flows.system(flow.phases[1]) +end + +function Flows.integrator(flow::MultiPhaseStateFlow) + return Flows.integrator(flow.phases[1]) +end +``` + +Inconvรฉnient : retourner le systรจme de la premiรจre phase est arbitraire et peut induire en erreur. + +#### Option B โ€” Retourner un vecteur + +```julia +function Flows.system(flow::MultiPhaseStateFlow) + return [Flows.system(phase) for phase in flow.phases] +end + +function Flows.integrator(flow::MultiPhaseStateFlow) + return [Flows.integrator(phase) for phase in flow.phases] +end +``` + +Inconvรฉnient : rompt le contrat `system(flow)::AbstractSystem` (retourne un `Vector` au lieu d'un `AbstractSystem`). Nรฉcessite de changer la signature du contrat. + +#### Option C โ€” Wrappers MultiPhaseSystem et MultiPhaseIntegrator (recommandรฉe) + +Crรฉer deux types enveloppes qui agrรจgent les N composantes et exposent une interface unifiรฉe, y compris pour l'affichage : + +```julia +# Dans MultiPhase/multiphase_system.jl +struct MultiPhaseSystem{ + TD <: Common.TimeDependence, + VD <: Common.VariableDependence, + S <: Systems.AbstractSystem{TD, VD} +} <: Systems.AbstractSystem{TD, VD} + phases :: Vector{S} + switching_times :: Vector{<:Real} +end + +# Dans MultiPhase/multiphase_integrator.jl +struct MultiPhaseIntegrator{I <: Integrators.AbstractIntegrator} + phases :: Vector{I} +end +``` + +Le contrat `AbstractFlow` est alors satisfait proprement : + +```julia +function Flows.system(flow::MultiPhaseStateFlow) + return MultiPhaseSystem( + [Flows.system(p) for p in flow.phases], + flow.switching_times + ) +end + +function Flows.integrator(flow::MultiPhaseStateFlow) + return MultiPhaseIntegrator([Flows.integrator(p) for p in flow.phases]) +end +``` + +Ces wrappers permettent en plus d'implรฉmenter un `show` informatif : + +```julia +function Base.show(io::IO, sys::MultiPhaseSystem) + println(io, "MultiPhaseSystem with $(length(sys.phases)) phases:") + for (k, (s, t)) in enumerate(zip(sys.phases, [sys.switching_times; Inf])) + println(io, " Phase $k : $(typeof(s)), until t = $t") + end +end + +function Base.show(io::IO, int::MultiPhaseIntegrator) + println(io, "MultiPhaseIntegrator with $(length(int.phases)) integrators:") + for (k, i) in enumerate(int.phases) + println(io, " Phase $k : $(typeof(i))") + end +end +``` + +Note : `MultiPhaseSystem` ici n'est **pas** le `MultiPhaseSystem` de la discussion #144 (qui portait la logique d'intรฉgration). C'est un wrapper passif pour satisfaire le contrat et l'affichage. + +Les mรฉthodes appelables (l'API publique) se comportent comme un flot simple : + +```julia +# ร‰valuation point (dรฉlรจgue ร  call avec StatePointConfig) +function (f::MultiPhaseStateFlow)(t0, x0, tf; variable=nothing, unsafe=false) + config = Common.StatePointConfig(t0, x0, tf) + return Flows.call(f, config; variable, unsafe) +end + +# ร‰valuation trajectoire (dรฉlรจgue ร  call avec StateTrajectoryConfig) +function (f::MultiPhaseStateFlow)(tspan::Tuple, x0; variable=nothing, unsafe=false) + config = Common.StateTrajectoryConfig(tspan, x0) + return Flows.call(f, config; variable, unsafe) +end +``` + +--- + +## 5. Points Techniques ร  Rรฉsoudre + +### 5.1 Temps de Switching : Absolus vs Relatifs + +**Dรฉcision : temps absolus.** + +- Cohรฉrent avec l'API OptimalControl.jl (`f * (t1, g)`) +- ร‰vite l'ambiguรฏtรฉ sur l'origine des temps (t0 du premier flot ? t0 de l'appel ?) +- Permet de raisonner directement sur la timeline physique du problรจme + +```julia +# Temps absolus (recommandรฉ) +f = f1 * (1.0, f2) * (2.5, f3) +sol = f((0.0, 4.0), x0) +# Phase 1 : [0.0, 1.0], Phase 2 : [1.0, 2.5], Phase 3 : [2.5, 4.0] +``` + +**Validation ร  la construction** : vรฉrifier que les temps de switching sont strictement croissants. + +```julia +function _validate_switching_times(times) + for k in 2:length(times) + times[k] > times[k-1] || throw(Exceptions.IncorrectArgument( + "Switching times must be strictly increasing; got $(times[k-1]) โ‰ฅ $(times[k]) at index $k" + )) + end +end +``` + +### 5.2 Fusion des Rรฉsultats + +#### StatePointConfig + +Pas de fusion : on propage seulement l'รฉtat final d'une phase ร  l'autre. Rรฉsultat final = รฉtat final de la derniรจre phase. + +#### StateTrajectoryConfig โ€” Fusion Progressive + +La fusion se fait pendant la boucle, en accumulant deux vecteurs `t_all` et `u_all`. Le dernier point de chaque phase intermรฉdiaire est exclu pour รฉviter les doublons aux points de switching : + +```text +Phase 1 : t = [0.0, 0.3, 0.7, 1.0] โ†’ on prend [0.0, 0.3, 0.7] (sans 1.0) +Phase 2 : t = [1.0, 1.4, 2.0] โ†’ on prend [1.0, 1.4, 2.0] (tous, c'est la derniรจre) +Rรฉsultat: t = [0.0, 0.3, 0.7, 1.0, 1.4, 2.0] +``` + +Note : aprรจs l'application d'un saut, `u_all` contient l'รฉtat **avant saut** au temps de switching et l'รฉtat **aprรจs saut** au mรชme temps n'est pas stockรฉ (il devient la condition initiale de la phase suivante). Cela peut รชtre discutรฉ selon le besoin de l'utilisateur. + +### 5.3 Flexibilitรฉ des Types de Sauts + +Les sauts ne sont pas limitรฉs aux vecteurs. Tout type pour lequel `+` est dรฉfini est acceptรฉ : + +| Type de saut | Exemple | Cas d'usage | +| --- | --- | --- | +| `Float64` | `0.5` | ร‰tat scalaire | +| `Vector{Float64}` | `[1.0, 0.0]` | ร‰tat vectoriel classique | +| `Matrix{Float64}` | `Matrix(I, n, n)` | Transformation linรฉaire | +| `Dual` | `Dual(1.0, 1.0)` | Diffรฉrentiation automatique | + +La contrainte est vรฉrifiรฉe ร  l'exรฉcution par Julia : si `z + ฮ”x` n'est pas dรฉfini, une `MethodError` est levรฉe. + +### 5.4 Localisation dans l'Architecture v1 : Submodule MultiPhase + +Plutรดt que de surcharger le submodule `Flows`, la concatรฉnation et tous ses types associรฉs vivent dans un **submodule dรฉdiรฉ `MultiPhase`**, chargรฉ en dernier (aprรจs `Flows`). + +#### Justification + +- `Flows` reste focalisรฉ sur un flot simple (`AbstractFlow`, `Flow`, `call`) +- `MultiPhase` a ses propres responsabilitรฉs (wrappers, concatรฉnation, intรฉgration multi-phase) +- La sรฉparation facilite les tests et la maintenance +- Cohรฉrent avec le principe SRP (Single Responsibility) + +#### Nouveau Layout + +```text +src/ +โ”œโ”€โ”€ CTFlows.jl # top-level manifest +โ”œโ”€โ”€ Common/Common.jl +โ”œโ”€โ”€ Data/Data.jl +โ”œโ”€โ”€ Systems/Systems.jl +โ”œโ”€โ”€ Integrators/Integrators.jl +โ”œโ”€โ”€ Solutions/Solutions.jl +โ”œโ”€โ”€ Flows/ +โ”‚ โ”œโ”€โ”€ Flows.jl # manifest Flows (inchangรฉ dans ses responsabilitรฉs) +โ”‚ โ”œโ”€โ”€ abstract_flow.jl # AbstractFlow + AbstractStateFlow + AbstractHamiltonianFlow +โ”‚ โ”œโ”€โ”€ flow.jl # Flow <: AbstractStateFlow ou AbstractHamiltonianFlow +โ”‚ โ”œโ”€โ”€ building.jl +โ”‚ โ””โ”€โ”€ calling.jl +โ””โ”€โ”€ MultiPhase/ + โ”œโ”€โ”€ MultiPhase.jl # manifest du submodule + โ”œโ”€โ”€ multiphase_system.jl # MultiPhaseSystem (wrapper passif) + โ”œโ”€โ”€ multiphase_integrator.jl # MultiPhaseIntegrator (wrapper passif) + โ”œโ”€โ”€ multiphase_flow.jl # MultiPhaseStateFlow, MultiPhaseHamiltonianFlow + โ”œโ”€โ”€ concatenation.jl # opรฉrateurs *, fonctions _flatten_* + โ””โ”€โ”€ calling.jl # call() pour les types multi-phase +``` + +#### Manifest MultiPhase/MultiPhase.jl + +```julia +""" +Submodule handling multi-phase flows and flow concatenation. + +Provides: +- `MultiPhaseSystem`, `MultiPhaseIntegrator` : passive wrappers for the `AbstractFlow` contract +- `MultiPhaseStateFlow`, `MultiPhaseHamiltonianFlow` : concatenated flow types +- `*` operator for building multi-phase flows +""" +module MultiPhase + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using ..Common +using ..Systems +using ..Integrators +using ..Flows + +include(joinpath(@__DIR__, "multiphase_system.jl")) +include(joinpath(@__DIR__, "multiphase_integrator.jl")) +include(joinpath(@__DIR__, "multiphase_flow.jl")) +include(joinpath(@__DIR__, "concatenation.jl")) +include(joinpath(@__DIR__, "calling.jl")) + +export MultiPhaseSystem, MultiPhaseIntegrator +export MultiPhaseStateFlow, MultiPhaseHamiltonianFlow + +end # module MultiPhase +``` + +#### Modification de CTFlows.jl + +```julia +module CTFlows + +include(joinpath(@__DIR__, "Common", "Common.jl")); using .Common +include(joinpath(@__DIR__, "Data", "Data.jl")); using .Data +include(joinpath(@__DIR__, "Systems", "Systems.jl")); using .Systems +include(joinpath(@__DIR__, "Integrators", "Integrators.jl")); using .Integrators +include(joinpath(@__DIR__, "Solutions", "Solutions.jl")); using .Solutions +include(joinpath(@__DIR__, "Flows", "Flows.jl")); using .Flows +include(joinpath(@__DIR__, "MultiPhase", "MultiPhase.jl")); using .MultiPhase # aprรจs Flows + +end # module CTFlows +``` + +L'ordre topologique est respectรฉ : `MultiPhase` dรฉpend de `Flows`, `Systems`, `Integrators`. + +#### Modification de Flows/abstract_flow.jl + +Seuls `AbstractStateFlow` et `AbstractHamiltonianFlow` s'ajoutent ici. `Flow` hรฉrite de l'un des deux : + +```julia +# Dans src/Flows/abstract_flow.jl +abstract type AbstractStateFlow{TD, VD, S <: Systems.AbstractSystem{TD,VD}} <: AbstractFlow{TD, VD} end +abstract type AbstractHamiltonianFlow{TD, VD, S <: Systems.AbstractSystem{TD,VD}} <: AbstractFlow{TD, VD} end +``` + +```julia +# Dans src/Flows/Flows.jl โ€” exports ร  ajouter +export AbstractStateFlow, AbstractHamiltonianFlow +``` + +#### Extension CTFlowsSciML + +`_build_merged_solution` reste un stub dans `MultiPhase/calling.jl` et son implรฉmentation rรฉelle va dans l'extension : + +```julia +# Dans ext/CTFlowsSciML.jl +function CTFlows.MultiPhase._build_merged_solution(t, u, flow) + first_phase = flow.phases[1] + prob = Integrators.last_prob(first_phase) + alg = Integrators.last_alg(first_phase) + return SciMLBase.build_solution(prob, alg, t, u; retcode=:Success) +end +``` + +--- + +## 6. Comparaison des Approches + +| Critรจre | Ancienne (ร  la volรฉe) | OptimalControl.jl | Proposition (sรฉquentielle) | +| --- | --- | --- | --- | +| Exactitude numรฉrique | โŒ Non exacte | โŒ Non exacte | โœ… Exacte | +| Stabilitรฉ numรฉrique | โŒ Erreurs croissantes | โŒ Erreurs croissantes | โœ… Stable | +| Complexitรฉ implรฉmentation | โœ… Simple | โœ… Simple | โš ๏ธ Modรฉrรฉe | +| Un intรฉgrateur par phase | โŒ Non | โŒ Non | โœ… Oui | +| API utilisateur | โœ… `*` | โœ… `*` | โœ… `*` | +| Niveau d'API | Flots | Flots | Flots | +| Type safety | โŒ Runtime | โŒ Runtime | โœ… Compile-time | +| Gestion mรฉmoire | โœ… O(1) | โœ… O(1) | โœ… O(N_points) | +| Sauts flexibles | โš ๏ธ Vecteurs | โš ๏ธ Vecteurs | โœ… Tout type + | +| Sauts hamiltoniens (ฮ”p) | โŒ Uniquement ฮท | โŒ Via jump | โœ… (ฮ”p) ou (ฮ”x, ฮ”p) | +| Chaรฎnage associatif | โš ๏ธ Emboรฎtement | โš ๏ธ Emboรฎtement | โœ… Aplatissement | + +--- + +## 7. Recommandation Finale + +**Adopter l'approche sรฉquentielle au niveau des flots avec hiรฉrarchie de types et contrainte compile-time.** + +### Justification + +1. **Exactitude** : seule approche garantissant une intรฉgration exacte (chaque phase dรฉmarre proprement) +2. **Type safety** : la compatibilitรฉ des flots est garantie par le compilateur via le paramรจtre `S` +3. **Flexibilitรฉ** : chaque phase peut avoir son propre intรฉgrateur et ses propres options +4. **API naturelle** : opรฉrateur `*` cohรฉrent avec OptimalControl.jl, chaรฎnable +5. **Distinction hamiltonien/รฉtat** : convention de saut claire selon le sous-type + +### Ordre d'Implรฉmentation + +1. `AbstractStateFlow` et `AbstractHamiltonianFlow` dans `abstract_flow.jl` +2. `Flow` hรฉrite du bon sous-type abstrait selon son systรจme +3. `MultiPhaseStateFlow` et `MultiPhaseHamiltonianFlow` avec leurs invariants +4. Opรฉrateurs `*` avec `_flatten_*` pour le chaรฎnage associatif +5. `call()` pour `StatePointConfig` (simple) +6. `call()` pour `StateTrajectoryConfig` (fusion progressive) +7. Stub `_build_merged_solution` dans `src/` + implรฉmentation dans `CTFlowsSciML` +8. Tests : contrats, sauts, chaรฎnage, exactitude vs approche ร  la volรฉe diff --git a/reports/save/concatenation/multiphase-plan.md b/reports/save/concatenation/multiphase-plan.md new file mode 100644 index 00000000..b0a726c3 --- /dev/null +++ b/reports/save/concatenation/multiphase-plan.md @@ -0,0 +1,272 @@ +# Implรฉmentation de la Concatรฉnation de Flots (MultiPhase) + +Ce plan implรฉmente l'intรฉgration sรฉquentielle exacte pour la concatรฉnation de flots, en ajoutant une hiรฉrarchie `AbstractStateSystem`/`AbstractHamiltonianSystem`, deux configs hamiltoniens, `StateFlow`/`HamiltonianFlow` en remplacement de `Flow`, et un nouveau submodule `MultiPhase` dรฉdiรฉ ร  la concatรฉnation. + +## Ce qui change et pourquoi + +- **`Systems`** : `AbstractStateSystem` et `AbstractHamiltonianSystem` sous `AbstractSystem`. `VectorFieldSystem <: AbstractStateSystem`. Permet la contrainte compile-time sur `S` dans `AbstractStateFlow` / `AbstractHamiltonianFlow`. +- **`Common`** : `HamiltonianPointConfig(t0, x0, p0, tf)` et `HamiltonianTrajectoryConfig(tspan, x0, p0)`. `initial_condition` retourne `vcat(x0, p0)`. +- **`Flows`** : `AbstractStateFlow{TD,VD,S<:AbstractStateSystem}` et `AbstractHamiltonianFlow{TD,VD,S<:AbstractHamiltonianSystem}`. `Flow` renommรฉ en `StateFlow`, `HamiltonianFlow` crรฉรฉ. `build_flow` dispatche selon le type de `sys`. +- **`MultiPhase`** : Nouveau submodule (`src/MultiPhase/`). Contient `MultiPhaseSystem`, `MultiPhaseIntegrator` (wrappers passifs pour le contrat `AbstractFlow`), `MultiPhaseStateFlow`, `MultiPhaseHamiltonianFlow`, opรฉrateurs `*`, et `call()` sรฉquentiel. +- **Tests** : 7 fichiers existants ร  mettre ร  jour (`Flow` โ†’ `StateFlow`). 1 nouveau fichier de tests MultiPhase. + +## Graphe de dรฉpendances aprรจs modification + +```text +Common (+ HamiltonianPointConfig, HamiltonianTrajectoryConfig) + โ”œโ”€โ”€ Data + โ”œโ”€โ”€ Systems (+ AbstractStateSystem, AbstractHamiltonianSystem) + โ”œโ”€โ”€ Integrators + โ”œโ”€โ”€ Solutions + โ”œโ”€โ”€ Flows (+ AbstractStateFlow, AbstractHamiltonianFlow, StateFlow, HamiltonianFlow) + โ””โ”€โ”€ MultiPhase (dรฉpend de Flows, Systems, Integrators, Common) +``` + +--- + +### Step 0 โ€” Branch + +```bash +git checkout main && git pull +git checkout -b feature/multiphase-concatenation +``` + +--- + +## Phase 1 โ€” Hiรฉrarchie Systems et Common + +### Step 1 โ€” `src/Systems/abstract_system.jl` + +> ๐Ÿ“ Follow `architecture.md` โ€” OCP: nouvelles abstractions sans modifier le contrat existant. +> ๐Ÿ—๏ธ Follow `modules.md` โ€” pas d'exports ici, uniquement dans le manifest. + +- Ajouter `abstract type AbstractStateSystem{TD, VD} <: AbstractSystem{TD, VD} end` +- Ajouter `abstract type AbstractHamiltonianSystem{TD, VD} <: AbstractSystem{TD, VD} end` + +> โ›” Pas de docstrings dans cette รฉtape. + +### Step 2 โ€” `src/Systems/Systems.jl` + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” Exports en fin de manifest. + +- Ajouter `AbstractStateSystem`, `AbstractHamiltonianSystem` ร  l'`export`. + +### Step 3 โ€” `src/Systems/vector_field_system.jl` + +> ๐Ÿ“ Follow `architecture.md` โ€” LSP: sous-type concret doit honorer le contrat du parent. + +- Changer `struct VectorFieldSystem{...} <: AbstractSystem{TD, VD}` en `<: AbstractStateSystem{TD, VD}`. + +### Step 4 โ€” `src/Common/configs.jl` + +> ๐Ÿ“ Follow `architecture.md` โ€” ISP: configs Hamiltoniens sรฉparรฉs. + +- Ajouter `struct HamiltonianPointConfig{T0, X0, P0, TF} <: AbstractConfig{X0}` avec champs `t0, x0, p0, tf`. +- Ajouter `struct HamiltonianTrajectoryConfig{TS, X0, P0} <: AbstractConfig{X0}` avec champs `tspan, x0, p0`. +- Implรฉmenter `tspan` pour les deux nouveaux types. +- Implรฉmenter `initial_condition` : retourne `vcat(c.x0, c.p0)`. +- Implรฉmenter `Base.show` pour les deux. + +### Step 5 โ€” `src/Common/Common.jl` + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” Exports en fin de manifest. + +- Ajouter `HamiltonianPointConfig`, `HamiltonianTrajectoryConfig` ร  l'`export`. + +### Step 6 โ€” Test checkpoint 1 : Systems + Common + +> ๐Ÿงช Follow `testing-creation.md` โ€” structs fakes au top-level. +> โ–ถ๏ธ Follow `testing-execution.md`. + +- Dans `test/suite/systems/test_abstract_system.jl` : ajouter `FakeStateSystem <: Systems.AbstractStateSystem` et `FakeHamiltonianSystem <: Systems.AbstractHamiltonianSystem` au top-level ; ajouter `@testset "Hierarchy"` vรฉrifiant les relations de sous-typage. +- Lancer les tests ciblรฉs : + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems", "suite/common"])' \ + 2>&1 | tee /tmp/ctflows_phase1.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_phase1.log +``` + +--- + +## Phase 2 โ€” Hiรฉrarchie Flows + +### Step 7 โ€” `src/Flows/abstract_flow.jl` + +> ๐Ÿ“ Follow `architecture.md` โ€” OCP: nouvelles abstractions sans modifier `AbstractFlow`. +> ๐Ÿ—๏ธ Follow `modules.md` โ€” pas d'exports ici. + +- Ajouter `abstract type AbstractStateFlow{TD, VD, S<:Systems.AbstractStateSystem{TD,VD}} <: AbstractFlow{TD, VD} end` +- Ajouter `abstract type AbstractHamiltonianFlow{TD, VD, S<:Systems.AbstractHamiltonianSystem{TD,VD}} <: AbstractFlow{TD, VD} end` + +### Step 8 โ€” `src/Flows/flow.jl` + +> ๐Ÿ“ Follow `architecture.md` โ€” Multiple Dispatch: deux callables distincts selon le type de flot. + +- Renommer `struct Flow{TD,VD,S<:Systems.AbstractSystem,I} <: AbstractFlow` en `struct StateFlow{TD,VD,S<:Systems.AbstractStateSystem{TD,VD},I} <: AbstractStateFlow{TD,VD,S}` (champs `system::S`, `integrator::I` inchangรฉs). +- Ajouter `struct HamiltonianFlow{TD,VD,S<:Systems.AbstractHamiltonianSystem{TD,VD},I} <: AbstractHamiltonianFlow{TD,VD,S}` (mรชmes champs). +- Adapter `system`, `integrator`, `build_flow` pour les deux structs : + - `build_flow(sys::Systems.AbstractStateSystem, int)` โ†’ `StateFlow(sys, int)` + - `build_flow(sys::Systems.AbstractHamiltonianSystem, int)` โ†’ `HamiltonianFlow(sys, int)` +- Adapter le callable `StateFlow` : `(f::StateFlow)(t0, x0, tf; ...)` โ†’ `Common.StatePointConfig`. +- Ajouter les callables `HamiltonianFlow` : `(f)(t0, x0, p0, tf; ...)` โ†’ `Common.HamiltonianPointConfig` et `(f)(tspan, x0, p0; ...)` โ†’ `Common.HamiltonianTrajectoryConfig`. + +### Step 9 โ€” `src/Flows/building.jl` + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” entry point gรฉnรฉrique, pas de logique de dispatch ici. + +- **Garder** `function Flow(data::Data.VectorField; opts...)` **inchangรฉ** (dรฉlรจgue ร  `build_flow` qui dispatche). + +### Step 10 โ€” `src/Flows/Flows.jl` + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” Exports en fin de manifest. + +- Remplacer `export AbstractFlow, Flow` par `export AbstractFlow, AbstractStateFlow, AbstractHamiltonianFlow, Flow, StateFlow, HamiltonianFlow`. (`Flow` reste exportรฉ : c'est la fonction publique `Flow(vf; opts...)`) +- Le reste des exports (`system`, `integrator`, `call`, `build_flow`) est inchangรฉ. + +### Step 11 โ€” Test checkpoint 2 : Flows + +> ๐Ÿงช Follow `testing-creation.md` โ€” renommage Flow โ†’ StateFlow dans tous les tests existants. +> โ–ถ๏ธ Follow `testing-execution.md`. + +- Remplacer `Flow` par `StateFlow` dans les 7 fichiers de tests impactรฉs : + - `test/suite/flows/test_flow.jl`, `test_abstract_flow.jl`, `test_flow_module.jl`, `test_building_flows.jl`, `test_calling.jl` + - `test/suite/extensions/test_forwarddiff_extension.jl`, `test_sciml_extension.jl` +- Lancer les tests ciblรฉs : + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/flows", "suite/extensions"])' \ + 2>&1 | tee /tmp/ctflows_phase2.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_phase2.log +``` + +--- + +## Phase 3 โ€” Submodule MultiPhase + +### Step 12 โ€” `src/CTFlows.jl` + `src/MultiPhase/MultiPhase.jl` (new) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” Chargement topologique : MultiPhase aprรจs Flows. + +- Crรฉer `src/MultiPhase/MultiPhase.jl` avec `module MultiPhase` : + - Imports : `Common`, `Systems`, `Integrators`, `Flows`, `DocStringExtensions`, `CTBase.Exceptions` + - `include` pour les 5 fichiers suivants (Steps 13โ€“16) + - Exports : `MultiPhaseSystem`, `MultiPhaseIntegrator`, `MultiPhaseStateFlow`, `MultiPhaseHamiltonianFlow` +- Dans `src/CTFlows.jl` : ajouter `include`/`using .MultiPhase` aprรจs `using .Flows`. + +### Step 13 โ€” `src/MultiPhase/multiphase_system.jl` + `multiphase_integrator.jl` (new) + +> ๐Ÿ“ Follow `architecture.md` โ€” SRP: wrappers passifs. + +- `MultiPhaseSystem{TD,VD,S<:Systems.AbstractSystem{TD,VD}} <: Systems.AbstractSystem{TD,VD}` : champs `phases::Vector{S}`, `switching_times::Vector{<:Real}` + `Base.show`. +- `MultiPhaseIntegrator{I<:Integrators.AbstractIntegrator}` : champ `phases::Vector{I}` + `Base.show`. + +### Step 14 โ€” `src/MultiPhase/multiphase_flow.jl` (new) + +> ๐Ÿ“ Follow `architecture.md` โ€” Parametric types. +> ๐Ÿ—๏ธ Follow `modules.md` โ€” Qualification des symboles siblings. + +- `MultiPhaseStateFlow{TD,VD,F<:Flows.AbstractStateFlow{TD,VD,S},S} <: Flows.AbstractStateFlow{TD,VD,S}` : `phases`, `switching_times`, `jumps::Vector{Union{Nothing,Any}}`. +- `MultiPhaseHamiltonianFlow{...} <: Flows.AbstractHamiltonianFlow{...}` : mรชmes champs, `jumps::Vector{Union{Nothing,Tuple{...}}}`. +- `Flows.system` et `Flows.integrator` retournent les wrappers `MultiPhaseSystem`/`MultiPhaseIntegrator`. +- Callables `(f)(t0, x0, tf; ...)` et `(f)(tspan, x0; ...)` dรฉlรฉguant ร  `call` (pour les deux types). + +### Step 15 โ€” `src/MultiPhase/concatenation.jl` (new) + +> ๐Ÿ“ Follow `architecture.md` โ€” OCP: extension via multiple dispatch. +> โš ๏ธ Follow `exceptions.md` โ€” `IncorrectArgument` pour temps non croissants. + +- `_flatten_phases`, `_flatten_times`, `_flatten_jumps` (dispatch `MultiPhase*Flow` vs `Abstract*Flow`). +- `_validate_switching_times` : throw `IncorrectArgument`. +- `Base.:*` pour `AbstractStateFlow` : 2 variants (sans saut, avec `ฮ”x`). +- `Base.:*` pour `AbstractHamiltonianFlow` : 3 variants (sans saut, `ฮ”p`, `(ฮ”x, ฮ”p)`). + +### Step 16 โ€” `src/MultiPhase/calling.jl` (new) + +> ๐Ÿ“ Follow `architecture.md` โ€” SRP. +> โš ๏ธ Follow `exceptions.md` โ€” stub `_build_merged_solution` throw `ExtensionError`. +> ๐Ÿ”ฌ Follow `type-stability.md` โ€” prรฉ-allouer `t_all`/`u_all`. + +- `_make_phase_config`, `_extract_final_state`, `_apply_jump` (dispatch State vs Hamiltonian). +- `call(flow, ::StatePointConfig)` : boucle sรฉquentielle. +- `call(flow, ::StateTrajectoryConfig)` : fusion progressive. +- Stub `_build_merged_solution` โ†’ `ExtensionError`. + +### Step 17 โ€” `ext/CTFlowsSciML.jl` + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” Qualification `CTFlows.MultiPhase._build_merged_solution`. + +- Implรฉmenter `CTFlows.MultiPhase._build_merged_solution(t, u, flow)` via `SciMLBase.build_solution`. + +### Step 18 โ€” Test checkpoint 3 : MultiPhase + +> ๐Ÿงช Follow `testing-creation.md` โ€” fakes au top-level, catรฉgories sรฉparรฉes. +> โ–ถ๏ธ Follow `testing-execution.md`. + +Crรฉer `test/suite/multiphase/test_multiphase.jl` : + +- Top-level : `FakeStateSystem <: Systems.AbstractStateSystem` + `Systems.rhs` ; `FakeStateFlow <: Flows.AbstractStateFlow` + `Flows.system`, `Flows.integrator`. +- `@testset "Wrappers"` : `MultiPhaseSystem`, `MultiPhaseIntegrator`, `show`. +- `@testset "Concatenation"` : `*` sans saut, avec saut, chaรฎnage 3 phases, aplatissement. +- `@testset "Type constraints"` : types `S` diffรฉrents โ†’ `MethodError`. +- `@testset "Validation"` : temps non croissants โ†’ `IncorrectArgument`. +- `@testset "call StatePointConfig"` et `@testset "call StateTrajectoryConfig"` avec flot trivial. +- `@testset "Extension stub"` : `_build_merged_solution` โ†’ `ExtensionError`. + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/multiphase"])' \ + 2>&1 | tee /tmp/ctflows_phase3.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_phase3.log +``` + +--- + +## Phase 4 โ€” Docstrings (quand tous les tests passent) + +### Step 19 โ€” Docstrings (tous les fichiers modifiรฉs) + +> ๐Ÿ“š Follow `docstrings.md` โ€” `$(TYPEDEF)` / `$(TYPEDSIGNATURES)`, sections complรจtes, exemples sรปrs. + +- `src/Systems/abstract_system.jl` โ€” `AbstractStateSystem`, `AbstractHamiltonianSystem` +- `src/Common/configs.jl` โ€” `HamiltonianPointConfig`, `HamiltonianTrajectoryConfig`, `tspan`, `initial_condition`, `Base.show` +- `src/Flows/abstract_flow.jl` โ€” `AbstractStateFlow`, `AbstractHamiltonianFlow` +- `src/Flows/flow.jl` โ€” `StateFlow`, `HamiltonianFlow`, callables, `build_flow` +- `src/MultiPhase/*.jl` โ€” tous les types et fonctions publics exportรฉs + +### Step 20 โ€” Vรฉrification finale + +> โ–ถ๏ธ Follow `testing-execution.md`. + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows")' 2>&1 | tee /tmp/ctflows_final.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_final.log +``` + +--- + +## Files summary + +**New**: +- `src/MultiPhase/MultiPhase.jl` +- `src/MultiPhase/multiphase_system.jl` +- `src/MultiPhase/multiphase_integrator.jl` +- `src/MultiPhase/multiphase_flow.jl` +- `src/MultiPhase/concatenation.jl` +- `src/MultiPhase/calling.jl` +- `test/suite/multiphase/test_multiphase.jl` + +**Modified**: +- `src/CTFlows.jl` โ€” ajout de MultiPhase (`modules.md`) +- `src/Systems/abstract_system.jl` โ€” +2 abstract types (`architecture.md`) +- `src/Systems/Systems.jl` โ€” exports (`modules.md`) +- `src/Systems/vector_field_system.jl` โ€” hรฉritage (`architecture.md`) +- `src/Common/configs.jl` โ€” +2 config types (`architecture.md`) +- `src/Common/Common.jl` โ€” exports (`modules.md`) +- `src/Flows/abstract_flow.jl` โ€” +2 abstract types (`architecture.md`) +- `src/Flows/flow.jl` โ€” renommage + HamiltonianFlow (`architecture.md`) +- `src/Flows/building.jl` โ€” dispatch build_flow (`modules.md`) +- `src/Flows/Flows.jl` โ€” exports (`modules.md`) +- `ext/CTFlowsSciML.jl` โ€” _build_merged_solution (`modules.md`) +- 7 fichiers de tests flows/extensions โ€” renommage Flowโ†’StateFlow + +**Deleted**: aucun diff --git a/reports/save/configs.md b/reports/save/configs.md new file mode 100644 index 00000000..ad67adbd --- /dev/null +++ b/reports/save/configs.md @@ -0,0 +1,448 @@ +# Config Trait Hierarchy โ€” Two-Dimensional Dispatch + +Remplace la hiรฉrarchie de types abstraits par un unique type racine ร  deux paramรจtres de type (structs tags subtypant `AbstractTag`) et quatre `const` aliases totalement symรฉtriques, permettant un dispatch orthogonal sur le mode (Point/Trajectory) ET le contenu (State/Hamiltonian). + +--- + +## Ce qui change et pourquoi + +**Objectif** : dispatchable sur le mode (Point vs Trajectory) ET le contenu (State vs Hamiltonian) de faรงon indรฉpendante et symรฉtrique โ€” aucune dimension n'est "primaire". + +**Approche : struct tags + `const` aliases (validรฉ conceptuellement en REPL)** + +```julia +# 2 abstraits intermรฉdiaires pour sรฉparer mode et contenu +abstract type AbstractModeTag <: AbstractTag end +abstract type AbstractContentTag <: AbstractTag end + +# 4 struct tags (vides, isbits), cohรฉrents avec AbstractTag existant (SciMLTag, Tsit5Tag) +struct PointTag <: AbstractModeTag end +struct TrajectoryTag <: AbstractModeTag end +struct StateTag <: AbstractContentTag end +struct HamiltonianTag <: AbstractContentTag end + +# Un seul type abstrait racine ร  3 paramรจtres avec contraintes explicites +abstract type AbstractConfig{X0, Mode<:AbstractModeTag, Content<:AbstractContentTag} end + +# Quatre aliases totalement symรฉtriques โ€” tous utilisables en dispatch ET isa +const AbstractPointConfig{X0, C} = AbstractConfig{X0, PointTag, C} +const AbstractTrajectoryConfig{X0, C} = AbstractConfig{X0, TrajectoryTag, C} +const AbstractStateConfig{X0, M} = AbstractConfig{X0, M, StateTag} +const AbstractHamiltonianConfig{X0, M} = AbstractConfig{X0, M, HamiltonianTag} + +# Types concrets subtypent DIRECTEMENT depuis AbstractConfig +struct StatePointConfig{T0<:Real, X0, TF<:Real} <: AbstractConfig{X0, PointTag, StateTag} end +struct StateTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0} <: AbstractConfig{X0, TrajectoryTag, StateTag} end +struct HamiltonianPointConfig{T0<:Real, X0, P0, TF<:Real} <: AbstractConfig{X0, PointTag, HamiltonianTag} end +struct HamiltonianTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0, P0} <: AbstractConfig{X0, TrajectoryTag, HamiltonianTag} end +``` + +**Patterns de dispatch disponibles :** + +| Pattern | Signification | +|---|---| +| `AbstractPointConfig` | tous les Point (State + Hamiltonian) | +| `AbstractTrajectoryConfig` | tous les Trajectory | +| `AbstractStateConfig` | tous les State (Point + Trajectory) | +| `AbstractHamiltonianConfig` | tous les Hamiltonian | +| `AbstractStateConfig{<:Number}` | State avec X0 scalaire | +| `AbstractHamiltonianConfig{<:Number}` | Hamiltonian avec X0 scalaire | + +**Pourquoi des struct tags plutรดt que des entiers ?** +- Cohรฉrent avec le pattern `AbstractTag` existant (`SciMLTag`, `Tsit5Tag`) +- Auto-documentรฉ : `PointTag` est plus clair que `1` +- Contrainte exprimable : `Mode<:AbstractModeTag, Content<:AbstractContentTag` +- Sรฉparation explicite : `AbstractModeTag` vs `AbstractContentTag` empรชche le mรฉlange accidentel + +**Mรฉthodes unifiรฉes (gains) :** + +- `tspan` : 4 mรฉthodes โ†’ 2 + stub +- `build_options` SciML : 4 mรฉthodes โ†’ 2 +- `_extract_initial_state` MultiPhase : 4 mรฉthodes โ†’ 2 +- `initial_condition` : suppression du `Union{...}` +- `initial_costate` : suppression du `Union{...}` + +**Ce qui disparaรฎt :** +- `abstract type AbstractPointConfig{X0} <: AbstractConfig{X0} end` (remplacรฉ par `const` alias) +- `abstract type AbstractTrajectoryConfig{X0} <: AbstractConfig{X0} end` (remplacรฉ par `const` alias) +- `Union{HamiltonianPointConfig, HamiltonianTrajectoryConfig}` dans `initial_condition` et `initial_costate` +- Les 4+4+4 mรฉthodes individuelles rรฉduites + +**Ce qui est ajoutรฉ :** +- `AbstractModeTag`, `AbstractContentTag` (intermรฉdiaires) dans `abstract_tag.jl` +- `PointTag`, `TrajectoryTag`, `StateTag`, `HamiltonianTag` (concrets) dans `abstract_tag.jl` +- `AbstractStateConfig`, `AbstractHamiltonianConfig` comme nouveaux exports +- `AbstractPointConfig`, `AbstractTrajectoryConfig` restent exportรฉs (backward compat) mais deviennent des `const` aliases + +--- + +## Graphe de dรฉpendances (inchangรฉ) + +```text +Common (abstract_tag.jl, configs.jl) + โ†“ +Integrators (abstract_integrator.jl) + โ†“ +CTFlowsSciML (extension) +Solutions (building.jl) +MultiPhase (calling.jl) +``` + +--- + +## Step 0 โ€” Branch + +```bash +git checkout develop && git pull +git checkout -b feature/config-trait-hierarchy +``` + +--- + +## Step 1 โ€” `src/Common/abstract_tag.jl` (modified) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” structs vides, subtypes de `AbstractTag`, exportรฉs depuis `Common.jl` +> ๐Ÿ“ Follow `architecture.md` โ€” marqueurs de traits, aucun comportement + +Ajouter aprรจs la dรฉfinition de `abstract type AbstractTag end` : + +```julia +# Intermรฉdiaires pour sรฉparer mode et contenu +abstract type AbstractModeTag <: AbstractTag end +abstract type AbstractContentTag <: AbstractTag end + +# Marqueurs de mode +struct PointTag <: AbstractModeTag end +struct TrajectoryTag <: AbstractModeTag end + +# Marqueurs de contenu +struct StateTag <: AbstractContentTag end +struct HamiltonianTag <: AbstractContentTag end +``` + +Ces 6 types sont des marqueurs de traits purs (zero bytes, isbits), cohรฉrents avec `SciMLTag` et `Tsit5Tag` qui existent dรฉjร . `AbstractModeTag` et `AbstractContentTag` empรชchent le mรฉlange accidentel de tags de dimensions diffรฉrentes. + +> โ›” Do NOT write docstrings in this step. + +--- + +## Step 2 โ€” `src/Common/Common.jl` (modified) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” mise ร  jour des exports + +Dans la ligne `export`, ajouter : +- `AbstractModeTag, AbstractContentTag` (intermรฉdiaires) +- `PointTag, TrajectoryTag, StateTag, HamiltonianTag` (concrets) +- `AbstractStateConfig, AbstractHamiltonianConfig` + +`AbstractPointConfig` et `AbstractTrajectoryConfig` restent dans les exports (ils deviennent des aliases mais les noms sont inchangรฉs). + +> โ›” Do NOT write docstrings in this step. + +--- + +## Step 3 โ€” `src/Common/configs.jl` (modified) + +> ๐Ÿ“ Follow `architecture.md` โ€” Open/Closed : les concrets ne changent que leur parent ; DRY : unification des mรฉthodes +> ๐Ÿ—๏ธ Follow `modules.md` โ€” tous les noms exportรฉs restent identiques +> ๐Ÿ”ฌ Follow `type-stability.md` โ€” information encodรฉe au niveau du type, zรฉro runtime check + +**Section "Abstract Types" โ€” remplacer les 3 dรฉclarations `abstract type` par :** + +```julia +# Un seul type abstrait racine avec contraintes explicites +abstract type AbstractConfig{X0, Mode<:AbstractModeTag, Content<:AbstractContentTag} end + +# Quatre aliases symรฉtriques (UnionAll) +const AbstractPointConfig{X0, C} = AbstractConfig{X0, PointTag, C} +const AbstractTrajectoryConfig{X0, C} = AbstractConfig{X0, TrajectoryTag, C} +const AbstractStateConfig{X0, M} = AbstractConfig{X0, M, StateTag} +const AbstractHamiltonianConfig{X0, M} = AbstractConfig{X0, M, HamiltonianTag} +``` + +**Section "StatePointConfig" โ€” changer le parent :** +- `struct StatePointConfig{T0<:Real, X0, TF<:Real} <: AbstractPointConfig{X0}` โ†’ `<: AbstractConfig{X0, PointTag, StateTag}` + +**Section "StateTrajectoryConfig" โ€” changer le parent :** +- `struct StateTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0} <: AbstractTrajectoryConfig{X0}` โ†’ `<: AbstractConfig{X0, TrajectoryTag, StateTag}` + +**Section "HamiltonianPointConfig" โ€” changer le parent :** +- `struct HamiltonianPointConfig{T0<:Real, X0, P0, TF<:Real} <: AbstractPointConfig{X0}` โ†’ `<: AbstractConfig{X0, PointTag, HamiltonianTag}` + +**Section "HamiltonianTrajectoryConfig" โ€” changer le parent :** +- `struct HamiltonianTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0, P0} <: AbstractTrajectoryConfig{X0}` โ†’ `<: AbstractConfig{X0, TrajectoryTag, HamiltonianTag}` + +**Section "Interface: tspan" โ€” 4 mรฉthodes โ†’ 2 + stub :** + +Supprimer les 4 mรฉthodes individuelles `tspan(::StatePointConfig)`, `tspan(::HamiltonianPointConfig)`, `tspan(::StateTrajectoryConfig)`, `tspan(::HamiltonianTrajectoryConfig)`. + +Remplacer par : +```julia +function tspan(c::AbstractPointConfig)::Tuple{Real, Real} + return (c.t0, c.tf) +end + +function tspan(c::AbstractTrajectoryConfig)::Tuple{Real, Real} + return c.tspan +end +``` + +Stub `tspan(c::AbstractConfig)` โ†’ `NotImplemented` inchangรฉ. + +**Section "Generic Accessor Functions" โ€” `initial_condition` :** + +Remplacer les 3 mรฉthodes actuelles par : +```julia +# State scalaire +function initial_condition(c::AbstractStateConfig{<:Number}) + return [c.x0] +end + +# State vecteur (fallback) +function initial_condition(c::AbstractStateConfig) + return c.x0 +end + +# Hamiltonien (scalaire et vecteur) +function initial_condition(c::AbstractHamiltonianConfig) + return vcat(c.x0, c.p0) +end +``` + +**Section "Generic Accessor Functions" โ€” `initial_costate` :** + +Remplacer les 2 mรฉthodes actuelles (dont la `Union`) par : +```julia +function initial_costate(c::AbstractHamiltonianConfig) + return c.p0 +end + +function initial_costate(c::AbstractStateConfig) + throw(Exceptions.PreconditionError( + "initial_costate is only defined for Hamiltonian configs"; + context = "initial_costate - requires Hamiltonian config", + reason = "config type $(typeof(c)) does not have a costate field", + suggestion = "use HamiltonianPointConfig or HamiltonianTrajectoryConfig instead", + )) +end +``` + +**`initial_state` :** inchangรฉ โ€” `c::Common.AbstractConfig` sans paramรจtre couvre tout. + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched; updated methods get `# TODO: docstring`. + +--- + +## Step 4 โ€” Test Checkpoint: Core Configs + +> ๐Ÿงช Follow `testing-creation.md` โ€” structs fake ร  top-level du module ; testsets sรฉparรฉs +> โ–ถ๏ธ Follow `testing-execution.md` + +Modifier `test/suite/common/test_configs.jl` : + +**Fake types (top-level) โ€” mettre ร  jour les parents :** +```julia +# Avant: +struct FakeConfig{X0} <: Common.AbstractConfig{X0} end +struct FakeConfigWithTspan{X0} <: Common.AbstractConfig{X0} end + +# Aprรจs: +struct FakeConfig{X0} <: Common.AbstractConfig{X0, Common.PointTag, Common.StateTag} end +struct FakeConfigWithTspan{X0} <: Common.AbstractConfig{X0, Common.PointTag, Common.StateTag} end +``` + +**Ajouter `@testset "Trait Aliases"` :** +```julia +Test.@testset "Trait Aliases" begin + # Mode aliases + Test.@test StatePointConfig <: Common.AbstractPointConfig + Test.@test HamiltonianPointConfig <: Common.AbstractPointConfig + Test.@test StateTrajectoryConfig <: Common.AbstractTrajectoryConfig + Test.@test HamiltonianTrajectoryConfig <: Common.AbstractTrajectoryConfig + + # Content aliases + Test.@test StatePointConfig <: Common.AbstractStateConfig + Test.@test StateTrajectoryConfig <: Common.AbstractStateConfig + Test.@test HamiltonianPointConfig <: Common.AbstractHamiltonianConfig + Test.@test HamiltonianTrajectoryConfig <: Common.AbstractHamiltonianConfig + + # Negative checks + Test.@test !(StatePointConfig <: Common.AbstractHamiltonianConfig) + Test.@test !(HamiltonianPointConfig <: Common.AbstractStateConfig) + Test.@test !(StatePointConfig <: Common.AbstractTrajectoryConfig) +end +``` + +**Vรฉrifier `tspan` unifiรฉ :** +```julia +Test.@testset "tspan unified dispatch" begin + sp = Common.StatePointConfig(0.0, [1.0], 1.0) + st = Common.StateTrajectoryConfig((0.0, 1.0), [1.0]) + hp = Common.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) + ht = Common.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0], [0.5]) + + Test.@test Common.tspan(sp) == (0.0, 1.0) + Test.@test Common.tspan(st) == (0.0, 1.0) + Test.@test Common.tspan(hp) == (0.0, 1.0) + Test.@test Common.tspan(ht) == (0.0, 1.0) +end +``` + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/common/test_configs"])' \ + 2>&1 | tee /tmp/ctflows_step4.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_step4.log +``` + +--- + +## Step 5 โ€” `ext/CTFlowsSciML.jl` (modified) + +> ๐Ÿ“ Follow `architecture.md` โ€” DRY : 4 mรฉthodes โ†’ 2 via dispatch sur les aliases + +**`build_options` โ€” remplacer les 4 mรฉthodes par 2 :** + +Supprimer : +- `Integrators.build_options(integ::SciML, config::Common.StatePointConfig)` +- `Integrators.build_options(integ::SciML, config::Common.HamiltonianPointConfig)` +- `Integrators.build_options(integ::SciML, config::Common.StateTrajectoryConfig)` +- `Integrators.build_options(integ::SciML, config::Common.HamiltonianTrajectoryConfig)` + +Remplacer par : +```julia +function Integrators.build_options(integ::SciML, config::Common.AbstractPointConfig) + return integ.options_point +end + +function Integrators.build_options(integ::SciML, config::Common.AbstractTrajectoryConfig) + return integ.options_trajectory +end +``` + +`build_options(integ::SciML, config::Nothing)` โ†’ inchangรฉ. + +> โ›” Do NOT write docstrings in this step. + +--- + +## Step 6 โ€” `src/MultiPhase/calling.jl` (modified) + +> ๐Ÿ“ Follow `architecture.md` โ€” DRY : 4 mรฉthodes โ†’ 2 via dispatch sur `AbstractStateConfig` / `AbstractHamiltonianConfig` + +**`_extract_initial_state` โ€” remplacer les 4 mรฉthodes par 2 :** + +Supprimer : +- `_extract_initial_state(config::Common.StatePointConfig)` +- `_extract_initial_state(config::Common.StateTrajectoryConfig)` +- `_extract_initial_state(config::Common.HamiltonianPointConfig)` +- `_extract_initial_state(config::Common.HamiltonianTrajectoryConfig)` + +Remplacer par : +```julia +_extract_initial_state(config::Common.AbstractStateConfig) = + Common.initial_state(config) + +_extract_initial_state(config::Common.AbstractHamiltonianConfig) = + (Common.initial_state(config), Common.initial_costate(config)) +``` + +> โ›” Do NOT write docstrings in this step. + +--- + +## Step 7 โ€” `src/Solutions/building.jl` (modified) + +> ๐Ÿ“ Follow `architecture.md` โ€” utiliser les aliases lร  oรน le dispatch est unique + +Les 5 mรฉthodes `build_solution` restent nรฉcessaires (elles combinent systรจme + config). Mettre ร  jour les signatures : + +```julia +# Scalar State Point +build_solution(result, sys::VectorFieldSystem, config::Common.AbstractStateConfig{<:Number, PointTag}) +# โ†’ return final_state(result)[1] (PointTag importรฉ ou qualifiรฉ) + +# Vector State Point +build_solution(result, sys::VectorFieldSystem, config::Common.AbstractPointConfig{<:Any, StateTag}) +# โ†’ return final_state(result) + +# State Trajectory +build_solution(result, sys::VectorFieldSystem, config::Common.AbstractTrajectoryConfig{<:Any, StateTag}) +# โ†’ return VectorFieldSolution(result) + +# Hamiltonian Point +build_solution(result, sys::HamiltonianVectorFieldSystem, config::Common.AbstractPointConfig{<:Any, HamiltonianTag}) +# โ†’ return _ham_split_solution(...) + +# Hamiltonian Trajectory +build_solution(result, sys::HamiltonianVectorFieldSystem, config::Common.AbstractTrajectoryConfig{<:Any, HamiltonianTag}) +# โ†’ return HamiltonianVectorFieldSolution(result) +``` + +Note : `PointTag`, `TrajectoryTag`, `StateTag`, `HamiltonianTag` sont dans `Common`, donc s'รฉcrivent `Common.PointTag` etc. dans ce fichier. + +> โ›” Do NOT write docstrings in this step. + +--- + +## Step 8 โ€” Test Checkpoint: Full test suite + +> ๐Ÿงช Follow `testing-creation.md` โ€” vรฉrifier exports nouveaux dans `test_common_module.jl` +> โ–ถ๏ธ Follow `testing-execution.md` + +Modifier `test/suite/common/test_common_module.jl` : +- Ajouter vรฉrification que `AbstractStateConfig`, `AbstractHamiltonianConfig`, `PointTag`, `TrajectoryTag`, `StateTag`, `HamiltonianTag` sont exportรฉs depuis `Common` + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows")' 2>&1 | tee /tmp/ctflows_step8.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_step8.log +``` + +--- + +## Step 9 โ€” Docstrings (all modified files) + +> ๐Ÿ“š Follow `docstrings.md` โ€” `$(TYPEDEF)` / `$(TYPEDSIGNATURES)`, sections complรจtes, cross-refs + +Fichiers et symboles : + +- `src/Common/abstract_tag.jl` โ€” `PointTag`, `TrajectoryTag`, `StateTag`, `HamiltonianTag` +- `src/Common/configs.jl` : + - `AbstractConfig{X0, Mode, Content}` โ€” documenter les 3 paramรจtres + les 4 aliases + - `AbstractPointConfig`, `AbstractTrajectoryConfig`, `AbstractStateConfig`, `AbstractHamiltonianConfig` โ€” documenter comme aliases + - `tspan` (2 mรฉthodes unifiรฉes) + - `initial_condition` (3 mรฉthodes : scalar state, vector state, hamiltonian) + - `initial_costate` (2 mรฉthodes : hamiltonian, stateโ†’PreconditionError) +- `ext/CTFlowsSciML.jl` โ€” `build_options` (2 mรฉthodes) +- `src/MultiPhase/calling.jl` โ€” `_extract_initial_state` (2 mรฉthodes) +- `src/Solutions/building.jl` โ€” `build_solution` (5 mรฉthodes, signatures mises ร  jour) + +--- + +## Step 10 โ€” Run tests + +> โ–ถ๏ธ Follow `testing-execution.md` + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/ctflows_config_trait.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_config_trait.log +``` + +Attendu : tous les tests passent, zรฉro failure, zรฉro error. + +--- + +## Files summary + +**Modified :** +- `src/Common/abstract_tag.jl` โ€” 4 nouveaux struct tags (`modules.md`) +- `src/Common/Common.jl` โ€” exports mis ร  jour (`modules.md`) +- `src/Common/configs.jl` โ€” hiรฉrarchie redessinรฉe, 12 mรฉthodes โ†’ 6 (`architecture.md`, `modules.md`) +- `ext/CTFlowsSciML.jl` โ€” `build_options` 4โ†’2 (`architecture.md`) +- `src/MultiPhase/calling.jl` โ€” `_extract_initial_state` 4โ†’2 (`architecture.md`) +- `src/Solutions/building.jl` โ€” signatures vers aliases (`architecture.md`) +- `test/suite/common/test_configs.jl` โ€” fake types + tests traits (`testing-creation.md`) +- `test/suite/common/test_common_module.jl` โ€” vรฉrification exports (`testing-creation.md`) + +**New :** aucun + +**Deleted :** aucun (les mรฉthodes supprimรฉes sont remplacรฉes, pas des fichiers) diff --git a/reports/save/inplace.md b/reports/save/inplace.md new file mode 100644 index 00000000..c305bfde --- /dev/null +++ b/reports/save/inplace.md @@ -0,0 +1,494 @@ +on va ajouter la possibilitรฉ de fournir une fonction in-place dans @vector_field.jl#L35-38 @hamiltonian_vector_field.jl#L37-40 . + +Il faudra ajouter un trait InPlaceTrait vs OutOfPlaceTrait sous types de @abstract_trait.jl#L51-52. Le trait doit apparaitre je pense dans @abstract_vector_field.jl#L36-37. On pourrait mรชme d'abord crรฉer un sous-types pour cette nouvelle notion de trait : AbstractFunctionPlaceTrait <: AbstractTrait, puis InPlaceTrait <: AbstractFunctionPlaceTrait, etc. + +Attention, le nom AbstractFunctionPlaceTrait est moche, il faut trouver autre chose. + +A la construction, on peut ajouter un kwarg : inplace = Common.__is_inplace() avec le dรฉfaut ร  false. Ou on peut dรฉtecter automatiquement, puisque l'on a dans @vector_field.jl#L71-72 @hamiltonian_vector_field.jl#L73-74 les deux autres traits : autonome et variable. A partir de ces deux traits, on sait quelle doit รชtre la signature de la fonction passรฉe en argument, cf. les signatures naturelles : @hamiltonian_vector_field.jl#L83-87 @vector_field.jl#L81-85. Du coup, on peut faire comme dans SciML et dรฉduire de la signature si la fonction est in-place ou out-of-place. + +Ensuite, une fois que l'on a ces nouveaux champs de vecteurs. Il faudra construire des systรจmes. @abstract_system.jl#L34-35 : un systรจme n'a pas le trait inplace vs outofplace. + +Remarque : on pourrait faire une vraie notion de trait comme @traits.jl#L1-395 et avoir une fonction has_function_place_trait comme @traits.jl#L87-88 qui renvoie une erreur par dรฉfaut, puis pour un VectorField et un HamiltonianVectorField, on crรฉe la fonction qui renvoie true, puis il faut crรฉer des fonctions pour rรฉcupรฉrer le trait comme @traits.jl#L113-114 et enfin il faut faire des fonctions sur les types qui ont le trait qui sont gรฉnรฉrique comme @traits.jl#L201-202 : on aurait is_inplace, is_outofplace (ร  voir pour les noms). + +Revenons aux systรจmes. Que le champs de vecteurs soit inplace ou non, il faudra toujours construire un rhs et un rhs_oop@vector_field_system.jl#L33-47 @hamiltonian_vector_field_system.jl#L58-71. C'est juste la construction qui est diffรฉrente. + +Si on a f! inplace et que l'on veut construire rhs_oop, il faut utiliser cet exemple @convert.jl#L35-48 concernant le similar. + +De plus, dans certains cas, on aura un u0 @CTFlowsSciML.jl#L571-588 qui ne sera pas mutable, donc quand on va faire @convert.jl#L40-41 , par exemple on va passer d'un SVector ร  un MVector. Dans ce cas, il faut revenir sur un SVector pour que tout fonctionne : @convert.jl#L43-44 avec @convert.jl#L33-34 . Pour ne pas faire if !ismutable(x) ร  chaque appel du rhs, on peut faire une version de rhs_oop classique mais on va remplacer le getter @hamiltonian_vector_field_system.jl#L232-235 @vector_field_system.jl#L126-129 en lui ajoutant un argument, is_u0_mutable, et nous on renvoie toujours rhs_oop classique sauf si le systรจme est inplace et que le u0 est non mutable, dans ce cas lร , on peut construire de maniรจre lazy le rhs_oop_finalize qui fait en plus @convert.jl#L43-44 ร  la fin. Pour les deux systรจmes bien sรปr. Aprรจs lazy ou pas, ร  voir, mais ce n'est pas le cas favorable, car si c'est non mutable, il vaut mieux fournir un systรจme out of place. On pourrait d'ailleurs ajouter un warning. + +Grรขce ร  tout รงa, on aura la possiblilitรฉ de faire de l'inplace que ce soit avec du mutable ou non, et donc dans les tests, on pourra ajouter des appels de flots (pour les tests d'intรฉgration) sur vecteur, matrice, complexe, StaticArrays, static matrices, etc. Pour les scalaires, รงa ne devrait pas marcher mais c'est pas grave car รงa n'a pas de sens de donner un f! implace si on fait du scalaire. + +Je pense qu'il n'y a pas ร  toucher au reste du code pour que รงa marche. + +Fais un plan trรจs dรฉtaillรฉ en suivant @plan.md . + +--- + +# In-Place Vector Field Support + +Ajouter le support des fonctions in-place (`f!`) dans `VectorField` et `HamiltonianVectorField` via un nouveau trait `AbstractMutabilityTrait` (`InPlace` / `OutOfPlace`), avec dรฉtection automatique depuis l'aritรฉ de la fonction, puis propager le changement jusqu'aux systรจmes et ร  l'extension SciML. + +--- + +## Ce qui change et pourquoi + +**Ajoutรฉ :** +- `AbstractMutabilityTrait <: AbstractTrait`, `InPlace`, `OutOfPlace` dans `Common` +- Trait accessors `has_mutability_trait`, `mutability_trait`, `is_inplace`, `is_outofplace` +- Troisiรจme paramรจtre de type `MD <: AbstractMutabilityTrait` dans `AbstractVectorField`, `VectorField`, `HamiltonianVectorField` +- Signatures naturelles et uniformes **in-place** pour les deux types de champs de vecteurs +- Champ `rhs_oop_finalize` dans `VectorFieldSystem` et `HamiltonianVectorFieldSystem` +- Signature `rhs_oop(sys, is_u0_mutable::Bool = true)` + +**Modifiรฉ :** +- Constructeurs `VectorField`/`HamiltonianVectorField` : auto-dรฉtection du trait via `first(methods(f)).nargs - 1` +- Constructeurs des systรจmes : deux branches de construction selon `InPlace` ou `OutOfPlace` +- `CTFlowsSciML.build_problem` : passe `rhs_oop(system, false)` quand `u0` est non-mutable + +**Invariant :** +- `AbstractSystem` ne porte pas le trait mutabilitรฉ (le systรจme expose toujours `rhs` + `rhs_oop`) +- Toutes les APIs existantes restent compatibles (kwarg `is_u0_mutable` default = `true`) + +--- + +## Signatures in-place de rรฉfรฉrence + +### VectorField (`f!` avec dx en premier, arity = oop_arity + 1) + +| TD / VD | OOP (existant) | InPlace (nouveau) | Uniforme IP | +|---|---|---|---| +| Aut, Fixed | `f(x)` | `f!(dx, x)` | `f!(dx, t, x, v)` | +| NonAut, Fixed | `f(t, x)` | `f!(dx, t, x)` | `f!(dx, t, x, v)` | +| Aut, NonFixed | `f(x, v)` | `f!(dx, x, v)` | `f!(dx, t, x, v)` | +| NonAut, NonFixed | `f(t, x, v)` | `f!(dx, t, x, v)` | (identique au naturel) | + +### HamiltonianVectorField (`f!` avec (dx, dp) sรฉparรฉs, arity = oop_arity + 2) + +| TD / VD | OOP (existant) | InPlace (nouveau) | Uniforme IP | +|---|---|---|---| +| Aut, Fixed | `f(x, p)` | `f!(dx, dp, x, p)` | `f!(dx, dp, t, x, p, v)` | +| NonAut, Fixed | `f(t, x, p)` | `f!(dx, dp, t, x, p)` | `f!(dx, dp, t, x, p, v)` | +| Aut, NonFixed | `f(x, p, v)` | `f!(dx, dp, x, p, v)` | `f!(dx, dp, t, x, p, v)` | +| NonAut, NonFixed | `f(t, x, p, v)` | `f!(dx, dp, t, x, p, v)` | (identique au naturel) | + +--- + +## Graphe de dรฉpendances + +``` +Common (AbstractMutabilityTrait, InPlace, OutOfPlace) + โ””โ”€โ”€ Data (AbstractVectorField{TD,VD,MD}, VectorField{F,TD,VD,MD}, HamiltonianVectorField{F,TD,VD,MD}) + โ””โ”€โ”€ Systems (VectorFieldSystem + rhs_oop_finalize, HamiltonianVectorFieldSystem) + โ””โ”€โ”€ ext/CTFlowsSciML (build_problem: rhs_oop(system, false)) +``` + +--- + +## Step 0 โ€” Branche + +```bash +git checkout develop && git pull +git checkout -b feat/inplace-vector-field +``` + +--- + +## Phase 1 โ€” Common : nouveau trait + +### Step 1 โ€” `src/Common/abstract_trait.jl` (modifiรฉ) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” ajouter aprรจs `AbstractContentTrait`, avant les structs concrรจtes de config +> ๐Ÿ“‹ Follow `architecture.md` โ€” SRP : la seule responsabilitรฉ est la dรฉclaration de l'abstraction + +- Ajouter `abstract type AbstractMutabilityTrait <: AbstractTrait end` +- Ajouter `struct InPlace <: AbstractMutabilityTrait end` +- Ajouter `struct OutOfPlace <: AbstractMutabilityTrait end` +- Mettre ร  jour le docstring de `AbstractTrait` pour mentionner `AbstractMutabilityTrait` + +> โ›” Pas de docstrings complets โ€” `# TODO: docstring` uniquement sur les nouvelles dรฉclarations. + +--- + +### Step 2 โ€” `src/Common/traits.jl` (modifiรฉ) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” mรชme style que `VariableDependence` / `Fixed` / `NonFixed` +> โš ๏ธ Follow `exceptions.md` โ€” fallbacks โ†’ `IncorrectArgument` / `NotImplemented` + +- Mettre ร  jour `_caller_function_name` : ajouter `"has_mutability_trait"` dans la liste des noms ร  ignorer (comme `has_time_dependence_trait` et `has_variable_dependence_trait`) +- Ajouter `has_mutability_trait(obj::Any)` โ†’ `Exceptions.IncorrectArgument` (fallback, mรชme patron que `has_time_dependence_trait`) +- Ajouter `mutability_trait(obj::Any)` โ†’ `Exceptions.NotImplemented` (fallback) +- Ajouter `is_inplace(obj::Any)` : appelle `has_mutability_trait(obj)`, puis `mutability_trait(obj) === InPlace` +- Ajouter `is_outofplace(obj::Any)` : appelle `has_mutability_trait(obj)`, puis `mutability_trait(obj) === OutOfPlace` +- Ajouter `is_inplace(::Type{InPlace}) = true`, `is_inplace(::Type{OutOfPlace}) = false` +- Ajouter `is_outofplace(::Type{InPlace}) = false`, `is_outofplace(::Type{OutOfPlace}) = true` + +> โ›” Pas de docstrings complets. + +--- + +### Step 3 โ€” `src/Common/Common.jl` (modifiรฉ) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” exports en fin de manifest, dans la section `export` + +- Exporter `AbstractMutabilityTrait`, `InPlace`, `OutOfPlace` +- Exporter `has_mutability_trait`, `mutability_trait` +- Exporter `is_inplace`, `is_outofplace` + +--- + +### Step 4 โ€” Test Checkpoint : Common mutability trait + +> ๐Ÿงช Follow `testing-creation.md` โ€” structs fakes au top-level, sรฉparation unit/contract/error +> โ–ถ๏ธ Follow `testing-execution.md` + +Modifier `test/suite/common/test_traits.jl` : + +- Au top-level : `struct FakeInPlace end`, `struct FakeOutOfPlace end`, `struct FakeNoMutability end` +- Implรฉmenter `Common.has_mutability_trait(::FakeInPlace) = true`, `Common.mutability_trait(::FakeInPlace) = Common.InPlace` (et idem pour `FakeOutOfPlace`) +- `@testset "Mutability Trait"` : + - `InPlace <: Common.AbstractMutabilityTrait` โ†’ true + - `OutOfPlace <: Common.AbstractMutabilityTrait` โ†’ true + - `Common.is_inplace(FakeInPlace())` โ†’ true + - `Common.is_outofplace(FakeInPlace())` โ†’ false + - `Common.is_inplace(FakeOutOfPlace())` โ†’ false + - `Common.is_inplace(FakeNoMutability())` โ†’ throws `IncorrectArgument` + - `Common.mutability_trait(FakeNoMutability())` โ†’ throws `NotImplemented` + +Modifier `test/suite/common/test_abstract_trait.jl` : +- Vรฉrifier `AbstractMutabilityTrait <: Common.AbstractTrait` +- Vรฉrifier `InPlace <: Common.AbstractMutabilityTrait` +- Vรฉrifier `OutOfPlace <: Common.AbstractMutabilityTrait` + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/common/test_traits"])' \ + 2>&1 | tee /tmp/ctflows_trait.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_trait.log +``` + +--- + +## Phase 2 โ€” Data : nouveau paramรจtre de type MD + +### Step 5 โ€” `src/Data/abstract_vector_field.jl` (modifiรฉ) + +> ๐Ÿ“‹ Follow `architecture.md` โ€” OCP : extension sans modification du patron de dispatch existant +> ๐Ÿ—๏ธ Follow `modules.md` โ€” utiliser `Common.AbstractMutabilityTrait` qualifiรฉ + +- Changer `AbstractVectorField{TD<:Common.TimeDependence, VD<:Common.VariableDependence}` en `AbstractVectorField{TD<:Common.TimeDependence, VD<:Common.VariableDependence, MD<:Common.AbstractMutabilityTrait}` +- Ajouter `Common.has_mutability_trait(::AbstractVectorField) = true` +- Ajouter `function Common.mutability_trait(vf::AbstractVectorField{<:Common.TimeDependence, <:Common.VariableDependence, MD}) where {MD} = MD` +- Mettre ร  jour les extracteurs existants `time_dependence`/`variable_dependence` (signature inchangรฉe, juste le param MD ajoutรฉ comme wildcard) + +> โ›” Pas de docstrings complets. + +--- + +### Step 6 โ€” `src/Data/vector_field.jl` (modifiรฉ) + +> ๐Ÿ“‹ Follow `architecture.md` โ€” OCP : nouvelles mรฉthodes par dispatch, pas de if/elseif sur le type +> ๐Ÿ—๏ธ Follow `modules.md` โ€” imports qualifiรฉs ; `Autonomous`, `NonAutonomous`, `Fixed`, `NonFixed` dรฉjร  en scope via `using ..Common` + +- Changer `VectorField{F<:Function, TD<:TimeDependence, VD<:VariableDependence}` en `VectorField{F<:Function, TD<:TimeDependence, VD<:VariableDependence, MD<:Common.AbstractMutabilityTrait}` +- Ajouter les fonctions internes `_oop_arity_vf(::Type{Autonomous}, ::Type{Fixed}) = 1` etc. (4 mรฉthodes) +- Ajouter `_detect_mutability_vf(f::Function, TD, VD)` : lit `first(methods(f)).nargs - 1`, compare ร  `oop_arity` et `oop_arity + 1`, lรจve `Exceptions.IncorrectArgument` si ambigu +- Mettre ร  jour le constructeur `VectorField(f; is_autonomous, is_variable)` : appelle `_detect_mutability_vf(f, TD, VD)` pour dรฉterminer `MD` +- Ajouter les signatures naturelles **in-place** (`(dx, x)`, `(dx, t, x)`, `(dx, x, v)`, `(dx, t, x, v)`) sur `VectorField{<:Function, TD, VD, InPlace}` +- Ajouter le call **uniforme in-place** `(dx, t, x, v)` pour les 3 combinaisons non-triviales de `InPlace` +- Mettre ร  jour `Base.show` pour afficher `MD` (ex : `mutability: InPlace`) + +> โ›” Pas de docstrings complets. + +--- + +### Step 7 โ€” `src/Data/hamiltonian_vector_field.jl` (modifiรฉ) + +> Mรชme patrons que Step 6, avec les spรฉcificitรฉs HVF + +- Changer `HamiltonianVectorField{F, TD, VD}` en `HamiltonianVectorField{F, TD, VD, MD<:Common.AbstractMutabilityTrait}` +- Ajouter `_oop_arity_hvf(TD, VD)` (4 mรฉthodes : oop_arity = 2, 3, 3, 4) +- Ajouter `_detect_mutability_hvf(f, TD, VD)` : compare ร  `oop_arity` (OOP) et `oop_arity + 2` (InPlace) +- Mettre ร  jour le constructeur `HamiltonianVectorField(f; is_autonomous, is_variable)` +- Ajouter les signatures naturelles in-place : `(dx, dp, x, p)`, `(dx, dp, t, x, p)`, `(dx, dp, x, p, v)`, `(dx, dp, t, x, p, v)` +- Ajouter le call uniforme in-place `(dx, dp, t, x, p, v)` pour les 3 combinaisons non-triviales +- Mettre ร  jour `Base.show` pour afficher `MD` + +> โ›” Pas de docstrings complets. + +--- + +### Step 8 โ€” Test Checkpoint : Data module + +> ๐Ÿงช Follow `testing-creation.md` โ€” structs fakes au top-level, isolation + +Modifier `test/suite/data/test_vector_field.jl` : +- `@testset "InPlace VectorField"` : + - Construire `VectorField((dx, x) -> (dx .= -x; nothing))` โ†’ `is_inplace(vf) == true`, `Common.mutability_trait(vf) === InPlace` + - Construire `VectorField(x -> -x)` โ†’ `is_outofplace(vf) == true` + - Tester le call naturel : `vf(dx, x)` remplit `dx` in-place + - Tester le call uniforme : `vf(dx, 0.0, x, nothing)` remplit `dx` + - Tester l'erreur sur aritรฉ incorrecte : `VectorField((a, b, c) -> nothing)` โ†’ `IncorrectArgument` + - Tester pour les 4 combinaisons (TD, VD) + +Modifier `test/suite/data/test_hamiltonian_vector_field.jl` : +- `@testset "InPlace HamiltonianVectorField"` : + - `HamiltonianVectorField((dx, dp, x, p) -> (dx .= x; dp .= -p; nothing))` โ†’ `is_inplace(hvf) == true` + - Call naturel et uniforme + - Aritรฉ incorrecte โ†’ `IncorrectArgument` + - 4 combinaisons (TD, VD) + +Modifier `test/suite/data/test_abstract_vector_field.jl` : +- `Common.has_mutability_trait(vf)` โ†’ true pour `VectorField` et `HamiltonianVectorField` +- `Common.mutability_trait(vf)` retourne le bon type + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/data"])' \ + 2>&1 | tee /tmp/ctflows_data.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_data.log +``` + +--- + +## Phase 3 โ€” Systems : rhs_oop_finalize + +### Step 9 โ€” `src/Systems/vector_field_system.jl` (modifiรฉ) + +> ๐Ÿ“‹ Follow `architecture.md` โ€” OCP : dispatch sur `MD` (OutOfPlace/InPlace), pas de if dans le constructeur +> ๐Ÿ—๏ธ Follow `modules.md` โ€” `Common.InPlace`, `Common.OutOfPlace` qualifiรฉs + +**Design `rhs_oop_finalize` :** + +- `rhs_oop_finalize::FINRHS` oรน **`FINRHS = Nothing` pour OOP VF** (finalize n'a pas de sens), et **`FINRHS = <closure>` pour InPlace VF** (construit eagerly) +- La dispatch sur `MD` rend l'accessor `rhs_oop` type-stable sans `isnothing` au runtime + +**Struct :** + +```julia +struct VectorFieldSystem{F, TD, VD, MD, RHS, OOPROHS, FINRHS} <: AbstractStateSystem{TD, VD} + vf::Data.VectorField{F, TD, VD, MD} + rhs::RHS + rhs_oop::OOPROHS # correct pour u0 mutable (InPlace) ou tout u0 (OOP) + rhs_oop_finalize::FINRHS # nothing (OOP) ou closure avec typeof(u)(dx) (InPlace) +end +``` + +**Helpers internes :** +- `_build_ip_rhs_vf(vf)` โ†’ `(du, u, ฮป, t) -> (vf(du, t, u, ฮป.variable); nothing)` (uniform IP call) +- `_build_oop_rhs_vf_ip(vf)` โ†’ `(u, ฮป, t) -> (dx = similar(u); vf(dx, t, u, ฮป.variable); dx)` (retourne dx, correct pour mutable) +- `_build_finalize_rhs_vf_ip(vf)` โ†’ `(u, ฮป, t) -> (dx = similar(u); vf(dx, t, u, ฮป.variable); typeof(u)(dx))` (correct pour immutable) + +**Deux constructeurs dispatching sur `MD` :** +- `VectorFieldSystem(vf::Data.VectorField{F, TD, VD, Common.OutOfPlace})` : `rhs` inchangรฉ (OOP call), `rhs_oop` = OOP call, `rhs_oop_finalize = nothing` +- `VectorFieldSystem(vf::Data.VectorField{F, TD, VD, Common.InPlace})` : `rhs` via `_build_ip_rhs_vf`, `rhs_oop` via `_build_oop_rhs_vf_ip`, `rhs_oop_finalize` via `_build_finalize_rhs_vf_ip` + +**Deux mรฉthodes `rhs_oop` dispatching sur `MD` (type-stable) :** + +```julia +# OOP VF : FINRHS = Nothing โ€” rhs_oop est toujours correct, ignorer is_u0_mutable +function rhs_oop(sys::VectorFieldSystem{F,TD,VD,Common.OutOfPlace,RHS,OOPROHS,Nothing}, + ::Bool = true) where {F,TD,VD,RHS,OOPROHS} + return sys.rhs_oop +end + +# InPlace VF : retourner finalize + warning si u0 immutable +function rhs_oop(sys::VectorFieldSystem{F,TD,VD,Common.InPlace,RHS,OOPROHS,FINRHS}, + is_u0_mutable::Bool = true) where {F,TD,VD,RHS,OOPROHS,FINRHS} + is_u0_mutable && return sys.rhs_oop + @warn "InPlace VectorField with immutable u0 (e.g. SVector): consider using an out-of-place function for better performance." maxlog=1 + return sys.rhs_oop_finalize +end +``` + +- Mettre ร  jour `Base.show` pour afficher `MD` (mutability) +- Mettre ร  jour `src/Systems/abstract_system.jl` : changer la signature du fallback `rhs_oop(sys::AbstractSystem)` en `rhs_oop(sys::AbstractSystem, ::Bool = true)` pour cohรฉrence avec la nouvelle API + +> โ›” Pas de docstrings complets. + +--- + +### Step 10 โ€” `src/Systems/hamiltonian_vector_field_system.jl` (modifiรฉ) + +> Mรชme design que Step 9 : `FINRHS = Nothing` pour OOP HVF, closure eagerly built pour InPlace HVF + +**Helpers InPlace HVF :** +- `_build_rhs_hvf_ip(hvf, ::Val{N})` โ€” splite `u` **et** `du` via `_ham_split`, passe les views ร  `hvf!(dx_view, dp_view, t, x, p, v)` : + ```julia + (du, u, ฮป, t) -> begin + x, p = _ham_split(u, N) + dx, dp = _ham_split(du, N) # views mutables dans du + hvf(dx, dp, t, x, p, ฮป.variable) # uniform IP call, remplit dx et dp + return nothing + end + ``` +- `_build_oop_rhs_hvf_ip(hvf, ::Val{N})` โ€” alloue dx/dp sรฉparรฉment, vcat final : + ```julia + (u, ฮป, t) -> begin + x, p = _ham_split(u, N) + dx, dp = similar(x), similar(p) + hvf(dx, dp, t, x, p, ฮป.variable) + return vcat(dx, dp) + end + ``` +- `_build_finalize_rhs_hvf_ip(hvf, ::Val{N})` โ€” idem + `typeof(u)` conversion : + ```julia + (u, ฮป, t) -> begin + x, p = _ham_split(u, N) + dx, dp = similar(x), similar(p) + hvf(dx, dp, t, x, p, ฮป.variable) + return typeof(u)(vcat(dx, dp)) + end + ``` + +**Deux constructeurs** pour chaque dimension (`state_dimension::Int` et `nothing`), dispatching sur `OutOfPlace`/`InPlace`. + +**Deux mรฉthodes `rhs_oop`** avec la mรชme logique que Step 9 (dispatch sur `Common.OutOfPlace`/`Common.InPlace`, warning dans la branche InPlace + `is_u0_mutable=false`). + +Mise ร  jour de `Base.show`. + +> โ›” Pas de docstrings complets. + +--- + +### Step 11 โ€” Test Checkpoint : Systems module + +> ๐Ÿงช Follow `testing-creation.md` + +Modifier `test/suite/systems/test_vector_field_system.jl` : +- `@testset "InPlace VectorFieldSystem"` : + - Construire `VectorFieldSystem(VectorField((dx, x) -> (dx .= -x; nothing)))` โ†’ sans erreur + - `rhs` fonctionne : `rhs!(du, u, p, t)` remplit correctement `du` + - `rhs_oop(sys)` fonctionne : retourne une valeur pour u mutable + - `rhs_oop(sys, true)` (mutable) retourne `Vector` + - `rhs_oop(sys, false)` (immutable) : appeler sur un `SVector` โ†’ retourne un `SVector` + - 4 combinaisons (TD, VD) + +Modifier `test/suite/systems/test_hamiltonian_vector_field_system.jl` : +- Analogues pour `HamiltonianVectorFieldSystem` avec InPlace HVF +- Tester `_ham_split` sur `du` pour les views mutables +- Tester `rhs_oop(sys, false)` avec un `SVector` combinรฉ + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems"])' \ + 2>&1 | tee /tmp/ctflows_systems.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_systems.log +``` + +--- + +## Phase 4 โ€” Extension SciML + +### Step 12 โ€” `ext/CTFlowsSciML.jl` (modifiรฉ) + +> ๐Ÿ—๏ธ Follow `modules.md` โ€” appel qualifiรฉ `Systems.rhs_oop` + +Dans `Integrators.build_problem`, branche `!ismutable(u0)` : +- Remplacer `f = Systems.rhs_oop(system)` par `f = Systems.rhs_oop(system, false)` + +Cela suffit : pour OOP VF, `rhs_oop(system, false)` retourne `rhs_oop` (identique ร  l'ancien comportement). Pour InPlace VF + u0 immutable, retourne `rhs_oop_finalize` + รฉmet le `@warn` (cรดtรฉ systรจme, Step 9). + +> โ›” Pas de docstrings complets. + +--- + +### Step 13 โ€” Test Checkpoint : Extension SciML + +> ๐Ÿงช Follow `testing-creation.md` + +Modifier `test/suite/extensions/test_sciml_extension.jl` ou `test_flow_callables_sciml.jl` : +- `@testset "InPlace VF โ€” SciML integration"` : + - Intรฉgration avec `VectorField((dx, x) -> dx .= -x)` (mutable `Vector{Float64}`) โ†’ mรชme rรฉsultat que OOP + - Intรฉgration avec `VectorField((dx, x) -> dx .= -x)` + `u0 = @SVector [1.0, 2.0]` โ†’ rรฉsultat `SVector` correct + - Analogues pour `HamiltonianVectorFieldSystem` avec InPlace HVF +- Vรฉrifier la prรฉsence du warning `@test_logs (:warn, r"InPlace")` pour le cas SVector + InPlace + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/extensions/test_sciml_extension"])' \ + 2>&1 | tee /tmp/ctflows_sciml.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_sciml.log +``` + +--- + +## Step 14 โ€” Docstrings (tous les fichiers modifiรฉs) + +> ๐Ÿ“š Follow `docstrings.md` โ€” `$(TYPEDEF)` / `$(TYPEDSIGNATURES)`, sections `# Arguments`, `# Returns`, `# Throws`, `# Example`, cross-refs `[@ref]` + +ร‰crire ou mettre ร  jour les docstrings pour chaque symbole nouveau ou modifiรฉ. + +### Localisation du design SciML (mutable/immutable u0) + +La documentation du pattern `rhs` / `rhs_oop` / `rhs_oop_finalize` est rรฉpartie ainsi : + +**`src/Systems/abstract_system.jl` โ€” `AbstractSystem` (docstring du type)** : endroit central pour expliquer le contrat global : +- Pourquoi deux accesseurs coexistent : `rhs` pour u0 mutable (SciML in-place), `rhs_oop` pour u0 immutable (SciML OOP) +- Le pattern gรฉnรฉral : SciML choisit le callable selon `ismutable(u0)` +- La distinction InPlace/OutOfPlace VF et son impact sur `rhs_oop_finalize` + +**`rhs_oop(sys, is_u0_mutable)` (mรฉthodes concrรจtes sur `VectorFieldSystem` et `HamiltonianVectorFieldSystem`)** : docstring dรฉtaillรฉe sur : +- Signification du paramรจtre `is_u0_mutable` (`true` = u0 mutable, `false` = u0 immutable) +- Comportement selon le trait : OOP VF ignore le paramรจtre ; InPlace VF retourne `rhs_oop_finalize` + warning si `false` +- Note de performance : InPlace VF + SVector est non-optimal, recommander OOP VF +- Cross-ref vers `rhs` et `AbstractSystem` + +**`rhs(sys)` (mรฉthodes concrรจtes)** : note brรจve โ€” *"Returns the in-place callable for use with mutable initial conditions in SciML. For immutable u0 (e.g. SVector), use `rhs_oop`."* + +### Liste complรจte des symboles ร  documenter + +- `src/Common/abstract_trait.jl` โ€” `AbstractMutabilityTrait`, `InPlace`, `OutOfPlace` +- `src/Common/traits.jl` โ€” `has_mutability_trait`, `mutability_trait`, `is_inplace`, `is_outofplace` (toutes les surcharges) +- `src/Data/abstract_vector_field.jl` โ€” `AbstractVectorField` (nouveau param), `has_mutability_trait`, `mutability_trait` +- `src/Data/vector_field.jl` โ€” `VectorField` (nouveau param, nouvelles calls), constructeur, `_detect_mutability_vf`, calls IP, `Base.show` +- `src/Data/hamiltonian_vector_field.jl` โ€” analogues +- `src/Systems/abstract_system.jl` โ€” `AbstractSystem` : design global `rhs`/`rhs_oop`, pattern SciML mutable/immutable, InPlace/OOP VF +- `src/Systems/vector_field_system.jl` โ€” `VectorFieldSystem` (nouveau champ), `rhs` (note SciML), `rhs_oop` (paramรจtre + finalize + warning), helpers internes +- `src/Systems/hamiltonian_vector_field_system.jl` โ€” analogues +- `ext/CTFlowsSciML.jl` โ€” `build_problem` (comportement รฉtendu) + +--- + +## Step 15 โ€” Run all tests + +> โ–ถ๏ธ Follow `testing-execution.md` + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows")' 2>&1 | tee /tmp/ctflows_inplace_final.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_inplace_final.log +``` + +**Attendu** : toutes les suites passent, zรฉro failure, zรฉro error. + +--- + +## Files summary + +**New :** aucun fichier crรฉรฉ. + +**Modified :** +- `src/Common/abstract_trait.jl` โ€” `AbstractMutabilityTrait`, `InPlace`, `OutOfPlace` (`architecture.md`) +- `src/Common/traits.jl` โ€” `has_mutability_trait`, `mutability_trait`, `is_inplace`, `is_outofplace`, update `_caller_function_name` (`modules.md`, `exceptions.md`) +- `src/Common/Common.jl` โ€” exports (`modules.md`) +- `src/Data/abstract_vector_field.jl` โ€” troisiรจme param `MD`, accessors (`architecture.md`) +- `src/Data/vector_field.jl` โ€” param `MD`, auto-detect, calls IP (`architecture.md`, `modules.md`) +- `src/Data/hamiltonian_vector_field.jl` โ€” idem (`architecture.md`, `modules.md`) +- `src/Systems/vector_field_system.jl` โ€” champ `rhs_oop_finalize`, constructeurs IP/OOP, `rhs_oop(sys, bool)` (`architecture.md`) +- `src/Systems/hamiltonian_vector_field_system.jl` โ€” idem (`architecture.md`) +- `ext/CTFlowsSciML.jl` โ€” `rhs_oop(system, false)` + warning (`modules.md`) +- `test/suite/common/test_abstract_trait.jl` โ€” `AbstractMutabilityTrait` tests (`testing-creation.md`) +- `test/suite/common/test_traits.jl` โ€” mutability trait tests (`testing-creation.md`) +- `test/suite/data/test_abstract_vector_field.jl` โ€” mutability accessor tests (`testing-creation.md`) +- `test/suite/data/test_vector_field.jl` โ€” InPlace VF tests (`testing-creation.md`) +- `test/suite/data/test_hamiltonian_vector_field.jl` โ€” InPlace HVF tests (`testing-creation.md`) +- `test/suite/systems/test_vector_field_system.jl` โ€” InPlace system tests (`testing-creation.md`) +- `test/suite/systems/test_hamiltonian_vector_field_system.jl` โ€” InPlace system tests (`testing-creation.md`) +- `test/suite/extensions/test_sciml_extension.jl` โ€” intรฉgration InPlace (`testing-creation.md`) + +**Deleted :** aucun. + +--- + +## Notes de design + +- **Dรฉtection auto** : `first(methods(f)).nargs - 1` donne l'aritรฉ positionnelle. Si `f` a plusieurs mรฉthodes avec des aritรฉs diffรฉrentes, `first` prend la plus rรฉcente/spรฉcifique. Cas ambigu โ†’ `IncorrectArgument`. +- **HVF InPlace** : `f!(dx, dp, x, p, ...)` (sorties sรฉparรฉes, +2 args). Le RHS SciML splite `u` et `du` en views via `_ham_split`. +- **rhs_oop_finalize** : pour InPlace VF + u0 immutable (SVector), retourne `typeof(u)(dx)` pour convertir MVector โ†’ SVector. Pour OOP VF : `rhs_oop_finalize = rhs_oop` (mรชme objet, pas de surcoรปt). +- **Warning** : รฉmis dans `rhs_oop(sys, false)` quand le VF est InPlace, pour guider vers l'usage OOP. +- **Scalaires** : non supportรฉs pour InPlace (pas de sens pour `similar(scalar)`). Aucune modification n'est nรฉcessaire ; l'erreur viendra naturellement de `similar`. diff --git a/reports/save/internalnorm/internalnorm_plan.md b/reports/save/internalnorm/internalnorm_plan.md new file mode 100644 index 00000000..3d8ecb12 --- /dev/null +++ b/reports/save/internalnorm/internalnorm_plan.md @@ -0,0 +1,282 @@ +# Add `internalnorm` for grid invariance under ForwardDiff (IND) + +## Contexte + +### IND โ€” Internal Numerical Differentiation + +L'**IND** (Internal Numerical Differentiation, ou Diffรฉrentiation Numรฉrique Interne) dรฉsigne la stratรฉgie qui consiste ร  diffรฉrentier *ร  l'intรฉrieur* de l'intรฉgrateur ODE, c'est-ร -dire ร  propager les nombres duaux ForwardDiff directement ร  travers les รฉtapes du solveur, plutรดt que de diffรฉrentier la solution aprรจs coup. + +La propriรฉtรฉ clรฉ de l'IND est la **grid invariance** : la grille temporelle adaptative `{tโ‚€, tโ‚, ..., tโ‚™}` choisie par le contrรดleur de pas doit รชtre **identique** ร  celle qu'on obtiendrait en intรฉgrant l'ODE originale en arithmรฉtique rรฉelle. Autrement dit, le contrรดleur de pas ne doit "voir" que la partie primale (`Float64`) de l'รฉtat, en ignorant les parties duales qui transportent l'information de sensibilitรฉ. + +Sans cette garantie, la grille change selon l'ordre de diffรฉrentiation, ce qui rend les dรฉrivรฉes numรฉriquement incohรฉrentes entre elles et produit des sensibilitรฉs inexactes, voire instables, dans les algorithmes de tir. + +### Le problรจme avec `ODE_DEFAULT_NORM` + +Lors de l'intรฉgration de `S(p0) = ฯ€_x(exp(tfยทHv)(x0, p0)) - xf` via ForwardDiff, l'รฉtat intรฉgrรฉ est de type `Dual{Tag, Float64, N}`. L'estimateur de pas doit donc porter sur la partie primale uniquement. + +SciML fournit dans `DiffEqBaseForwardDiffExt` des surcharges de `ODE_DEFAULT_NORM` pour les `Dual` : + +```julia +@inline ODE_DEFAULT_NORM(u::ForwardDiff.Dual, ::Any) = sqrt(sse(u)) +@inline function ODE_DEFAULT_NORM(u::AbstractArray{<:ForwardDiff.Dual{Tag, T}}, t) where {Tag, T} + return sqrt(DiffEqBase.__sum(sse, u; init = sse(zero(T))) / DiffEqBase.totallength(u)) +end +``` + +Le problรจme est dans `sse`. Dans DiffEqBase, `sse(x) = abs2(x)`. Or pour un `Dual` ForwardDiff : + +``` +abs2(Dual(v, pโ‚, pโ‚‚, ...)) = Dual(abs2(v), 2vยทpโ‚, 2vยทpโ‚‚, ...) +``` + +`sse` sur un `Dual` retourne donc **un `Dual`**, pas un `Float64`. La norme retourne elle-mรชme un `Dual`, et le contrรดleur de pas prend ses dรฉcisions accept/reject en fonction d'une valeur qui inclut les partials. **Les parties duales influencent la sรฉlection du pas**, ce qui brise la grid invariance โ€” mรชme au premier ordre. + +La `real_norm` basรฉe sur `deepvalue` corrige cela en garantissant que la norme retourne toujours un `Float64` pur, quelle que soit la profondeur d'imbrication des `Dual` : + +```julia +deepvalue(x::Real) = x # base case +deepvalue(x::ForwardDiff.Dual) = deepvalue(value(x)) # descend rรฉcursivement +``` + +De plus, avec `OrdinaryDiffEqTsit5` (le sous-paquet lรฉger utilisรฉ dans CTFlows), `DiffEqBaseForwardDiffExt` peut ne pas รชtre activรฉ du tout, laissant la norme retomber sur `norm(u)` qui inclut encore plus brutalement toutes les parties duales. + +**Objectif** : dรฉfinir une `real_norm` basรฉe sur `deepvalue` (extraction rรฉcursive de la partie primale) et l'enregistrer comme **valeur par dรฉfaut de l'option `internalnorm`** dans la stratรฉgie `SciML`. + +--- + +## Oรน vit quoi + +``` +CTFlowsSciML.jl + deepvalue(x) โ€” extraction rรฉcursive Float64 depuis Dual imbriquรฉs + real_norm(u, t) โ€” norme basรฉe sur deepvalue, conforme ร  l'interface SciML + + nouvelle OptionDefinition :internalnorm dans Strategies.metadata(::Type{SciML}) +``` + +`deepvalue` reste dans l'extension SciML pour l'instant (dรฉpend de ForwardDiff). Si le backend AD change (Enzyme, etc.), `deepvalue` migrera dans CTBase et sera redรฉfini par backend โ€” c'est l'objet de CTBase#25 / CTFlows#93. + +--- + +## Implรฉmentation + +### Step 0 โ€” Branche + +```bash +git checkout develop && git pull +git checkout -b feature/internalnorm-ind +``` + +### Step 1 โ€” Dรฉfinir `deepvalue` et `real_norm` dans `CTFlowsSciML.jl` + +Ajouter **avant** la dรฉfinition de `Strategies.metadata` : + +```julia +import ForwardDiff + +# Extraction rรฉcursive de la partie rรฉelle โ€” gรจre les Dual imbriquรฉs +# (ordre 1 : Dual{T, Float64, N}, ordre 2 : Dual{T1, Dual{T2, Float64, N2}, N1}, etc.) +deepvalue(x::Real) = x +deepvalue(x::ForwardDiff.Dual) = deepvalue(ForwardDiff.value(x)) + +# Norme interne basรฉe uniquement sur la partie primale +# Conforme ร  l'interface SciML : internalnorm(u, t) +real_norm(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(deepvalue.(u), t) +real_norm(u::ForwardDiff.Dual, t) = abs(deepvalue(u)) +real_norm(u::Real, t) = abs(u) +``` + +Ajouter `using DiffEqBase: DiffEqBase` dans les imports du module. + +### Step 2 โ€” Ajouter `internalnorm` comme option dans `Strategies.metadata(::Type{SciML})` + +Dans la liste des `OptionDefinition`, ajouter : + +```julia +Strategies.OptionDefinition(; + name = :internalnorm, + type = Any, + default = real_norm, + description = "Internal norm for adaptive step-size control. " * + "Defaults to `real_norm`, which extracts the primal (Float64) " * + "part of ForwardDiff dual numbers to ensure grid invariance (IND). " * + "Set to `DiffEqBase.ODE_DEFAULT_NORM` to use the SciML default.", + aliases = (:internal_norm, :norm), +), +``` + +Aucun `validator` ici โ€” la valeur est une fonction, difficile ร  valider statiquement. + +### Step 3 โ€” Vรฉrifier que `solve_problem` passe bien `internalnorm` + +`internalnorm` sera automatiquement inclus dans `Strategies.options_dict(integ)` et donc passรฉ ร  `SciMLBase.solve(prob; options...)`. Aucun changement dans `solve_problem`. Vรฉrifier quand mรชme que `options_dict` ne filtre pas les fonctions โ€” si c'est le cas, une adaptation de `CTSolvers` peut รชtre nรฉcessaire. + +--- + +## Tests + +### Step 4 โ€” `test/suite/extensions/test_internalnorm.jl` (nouveau fichier) + +#### Test 1 โ€” `deepvalue` descend correctement + +```julia +@testset "deepvalue" begin + using ForwardDiff + # Ordre 0 โ€” identitรฉ + @test CTFlowsSciML.deepvalue(1.0) === 1.0 + # Ordre 1 + d1 = ForwardDiff.Dual{:Tag1}(3.0, 1.0) + @test CTFlowsSciML.deepvalue(d1) === 3.0 + # Ordre 2 โ€” Dual imbriquรฉ + d2 = ForwardDiff.Dual{:Tag2}(d1, d1) + @test CTFlowsSciML.deepvalue(d2) === 3.0 +end +``` + +#### Test 2 โ€” `real_norm` ignore les parties duales + +```julia +@testset "real_norm is grid-invariant" begin + using ForwardDiff + u_real = [1.0, 2.0, 3.0] + u_dual = ForwardDiff.Dual{:T}.(u_real, ones(3)) # mรชmes valeurs, partials โ‰  0 + # La norme doit รชtre identique + @test CTFlowsSciML.real_norm(u_real, 0.0) โ‰ˆ CTFlowsSciML.real_norm(u_dual, 0.0) +end +``` + +#### Test 3 โ€” Grille DIFFร‰RENTE sans `real_norm` (rรฉgression) + +Ce test documente le problรจme original et garantit qu'il existe bien. + +```julia +@testset "grids differ WITHOUT real_norm (baseline)" begin + using ForwardDiff, OrdinaryDiffEqTsit5, SciMLBase + + f!(du, u, p, t) = (du .= u) # แบ‹ = x + u0_real = [1.0] + + # Intรฉgration rรฉelle + prob_real = ODEProblem(f!, u0_real, (0.0, 1.0), nothing) + sol_real = SciMLBase.solve(prob_real, Tsit5(); reltol=1e-8, abstol=1e-8, dense=false) + + # Intรฉgration avec Dual (Jacobienne par rapport ร  u0) + function integrate_dual(x0) + prob = ODEProblem(f!, x0, (0.0, 1.0), nothing) + return SciMLBase.solve(prob, Tsit5(); reltol=1e-8, abstol=1e-8, dense=false) + end + u0_dual = ForwardDiff.Dual{:T}.([1.0], [1.0]) + sol_dual = integrate_dual(u0_dual) + + # Les grilles DOIVENT รชtre diffรฉrentes sans real_norm + t_real = sol_real.t + t_dual = ForwardDiff.value.(sol_dual.t) + @test t_real โ‰  t_dual # documente le problรจme +end +``` + +#### Test 4 โ€” Grille IDENTIQUE avec `real_norm` (objectif) + +```julia +@testset "grids identical WITH real_norm" begin + using ForwardDiff, OrdinaryDiffEqTsit5, SciMLBase + + f!(du, u, p, t) = (du .= u) + u0_real = [1.0] + + prob_real = ODEProblem(f!, u0_real, (0.0, 1.0), nothing) + sol_real = SciMLBase.solve(prob_real, Tsit5(); + reltol=1e-8, abstol=1e-8, dense=false, + internalnorm=CTFlowsSciML.real_norm) + + function integrate_dual_with_norm(x0) + prob = ODEProblem(f!, x0, (0.0, 1.0), nothing) + return SciMLBase.solve(prob, Tsit5(); + reltol=1e-8, abstol=1e-8, dense=false, + internalnorm=CTFlowsSciML.real_norm) + end + u0_dual = ForwardDiff.Dual{:T}.([1.0], [1.0]) + sol_dual = integrate_dual_with_norm(u0_dual) + + t_real = sol_real.t + t_dual = ForwardDiff.value.(sol_dual.t) + @test t_real == t_dual # grid invariance garantie +end +``` + +#### Test 5 โ€” `real_norm` est bien la valeur par dรฉfaut de l'intรฉgrateur CTFlows + +```julia +@testset "real_norm is default internalnorm in SciML strategy" begin + integ = Integrators.build_integrator() + opts = Strategies.options_dict(integ) + @test haskey(opts, :internalnorm) + @test opts[:internalnorm] === CTFlowsSciML.real_norm +end +``` + +#### Test 6 โ€” Integration CTFlows end-to-end : Jacobienne de la fonction de tir + +```julia +@testset "Jacobian via CTFlows gives consistent grid" begin + using ForwardDiff, CTFlows + + f!(du, u, p, t) = (du[1] = u[2]; du[2] = -u[1]) # oscillateur harmonique + vf = VectorField(f!) + flow = Flow(vf) + + # Fonction de tir : intรจgre de t0 ร  tf, retourne l'รฉtat final + function shoot(p0) + x0 = [1.0, p0[1]] + return flow((0.0, 1.0), x0) # StateTrajectoryConfig + end + + # Jacobienne โ€” ForwardDiff seed des Dual dans l'รฉtat + J = ForwardDiff.jacobian(p0 -> CTFlows.Solutions.times(shoot(p0)), [0.5]) + # Si on arrive ici sans erreur et avec une grille cohรฉrente, c'est bon + @test J isa AbstractMatrix +end +``` + +### Step 5 โ€” Ajouter le fichier dans la suite de tests + +Dans `test/runtests.jl` ou le fichier d'inclusion des extensions : + +```julia +include("suite/extensions/test_internalnorm.jl") +``` + +--- + +## Vรฉrification + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/internalnorm.log +grep -E "Error|Fail|Test Summary" /tmp/internalnorm.log +``` + +--- + +## Fichiers + +**Modifiรฉs** : +- `ext/CTFlowsSciML.jl` โ€” ajout `deepvalue`, `real_norm`, option `internalnorm` + +**Nouveaux** : +- `test/suite/extensions/test_internalnorm.jl` + +--- + +## Note sur l'รฉvolution future (CTBase#25 / CTFlows#93) + +`deepvalue` est actuellement hard-codรฉ sur `ForwardDiff.Dual`. Quand le backend AD deviendra configurable (Enzyme, etc.), `deepvalue` migrera dans CTBase comme une fonction gรฉnรฉrique redรฉfinie par chaque backend : + +```julia +# CTBase โ€” interface gรฉnรฉrique +deepvalue(x::Real) = x +# CTFlowsForwardDiff โ€” implรฉmentation ForwardDiff +deepvalue(x::ForwardDiff.Dual) = deepvalue(ForwardDiff.value(x)) +# CTFlowsEnzymeExt โ€” implรฉmentation Enzyme (future) +deepvalue(x::Enzyme.Active) = ... +``` + +`real_norm` dans CTFlowsSciML appellera alors `CTBase.deepvalue` sans modification. diff --git a/reports/save/issue_93_flow_usage_examples.md b/reports/save/issue_93_flow_usage_examples.md new file mode 100644 index 00000000..f680794a --- /dev/null +++ b/reports/save/issue_93_flow_usage_examples.md @@ -0,0 +1,172 @@ +# CTFlows โ€” exemples pratiques de construction et d'appel des flots + +Ce guide montre comment crรฉer des flots depuis des donnรฉes (`VectorField`, `HamiltonianVectorField`) ou depuis des objets SciML (`ODEFunction`, `ODEProblem`), et comment les appeler. + +## Imports (un seul `using CTFlows` + qualification) + +```julia +using CTFlows +using OrdinaryDiffEq +``` + +On qualifie ensuite explicitement avec `CTFlows.*` (`CTFlows.Flow`, `CTFlows.VectorField`, etc.). + +--- + +## 1) `CTFlows.Flow(data::VectorField; opts...)` + +Correspond ร  `src/Flows/building.jl` (L29-L34). + +```julia +# x' = -x (autonome, sans paramรจtre variable) +vf = CTFlows.VectorField( + x -> -x; + is_autonomous=true, + is_variable=false, +) + +flow_vf = CTFlows.Flow( + vf; + alg=Tsit5(), + abstol=1e-10, + internalnorm=(u, t) -> sqrt(sum(abs2, u)), +) + +# Appel "point" (StateFlow): flow(t0, x0, tf) +xf = flow_vf(0.0, [1.0], 1.0) + +# Appel "trajectoire" (StateFlow): flow((t0, tf), x0) +sol_vf = flow_vf((0.0, 1.0), [1.0]) +mid_vf = sol_vf(0.5) +``` + +--- + +## 2) `CTFlows.Flow(data::HamiltonianVectorField; state_dimension=..., opts...)` + +Correspond ร  `src/Flows/building.jl` (L65-L70). + +```julia +# Systรจme hamiltonien simple: +# x' = p, p' = -x +hvf = CTFlows.HamiltonianVectorField( + (x, p) -> (p, -x); + is_autonomous=true, + is_variable=false, +) + +flow_hvf = CTFlows.Flow( + hvf; + state_dimension=1, + alg=Tsit5(), + abstol=1e-10, +) + +# Appel "point" (HamiltonianFlow): flow(t0, x0, p0, tf) +xf, pf = flow_hvf(0.0, [1.0], [0.0], 10.0) + +# Appel "trajectoire" (HamiltonianFlow): flow((t0, tf), x0, p0) +sol_h_traj = flow_hvf((0.0, 10.0), [1.0], [0.0]) +``` + +--- + +## 3) `CTFlows.Flow(f::AbstractODEFunction; opts...)` + +Correspond ร  `ext/CTFlowsSciML/flow_constructors.jl` (L31-L36). + +```julia +# u' = -p*u (non autonome en paramรจtre p) +f = ODEFunction((du, u, p, t) -> du .= -p .* u) + +flow_fun = CTFlows.Flow( + f; + alg=Tsit5(), + abstol=1e-12, + internalnorm=(u, t) -> maximum(abs, u), +) + +# Appel "point" (StateFlow) avec variable=p +xf_fun = flow_fun(0.0, [1.0], 1.0; variable=2.0) + +# Appel "trajectoire" (StateFlow) avec variable=p +sol_fun = flow_fun((0.0, 1.0), [1.0]; variable=2.0) +mid_fun = sol_fun(0.5) +``` + +--- + +## 4) `CTFlows.Flow(prob::AbstractODEProblem; opts...)` + +Correspond ร  `ext/CTFlowsSciML/flow_constructors.jl` (L65-L69). + +```julia +prob = ODEProblem( + (du, u, p, t) -> du .= -p .* u, + [1.0], + (0.0, 1.0), + 2.0, +) + +flow_prob = CTFlows.Flow( + prob; + alg=Tsit5(), + abstol=1e-12, +) + +# 4.a) Appel sans argument (rรฉsout le problรจme tel quel) +# unsafe=false : vรฉrifie le code de retour du solveur et lance une erreur si l'intรฉgration รฉchoue +# unsafe=true : ignore les erreurs d'intรฉgration (utile pour le dรฉbogage) +# Note: unsafe peut รชtre utilisรฉ sur tous les appels de flots (point/trajectoire, StateFlow/HamiltonianFlow/SciMLProblemFlow) +res0 = flow_prob(; unsafe=false) +xf0 = CTFlows.final_state(res0) + +# 4.b) Appel remake "point" : (t0, x0, tf; variable=...) +res1 = flow_prob(0.2, [2.0], 1.5; variable=3.0, unsafe=false) +xf1 = CTFlows.final_state(res1) +``` + +Remarque importante: il n'existe pas (encore) d'appel avec tuple `(t0, tf)` pour `SciMLProblemFlow`. + +```julia +# Actuellement non implรฉmentรฉ pour SciMLProblemFlow: +# flow_prob((0.0, 1.0), [1.0]) +``` + +--- + +## Options ร  passer au constructeur du flot + +Dans les 4 constructeurs ci-dessus, les options se passent au moment de `CTFlows.Flow(...)`. + +### Valeurs par dรฉfaut + +- `alg`: par dรฉfaut `Tsit5()` (nรฉcessite `OrdinaryDiffEqTsit5` ou un autre algorithme SciML chargรฉ). +- `abstol`: par dรฉfaut `1e-8`. +- `internalnorm`: par dรฉfaut `Common.real_norm`, qui extrait la partie primale (Float64) des nombres duaux ForwardDiff via `deepvalue` pour garantir lโ€™invariance de grille (IND) lors de lโ€™intรฉgration avec ForwardDiff. + +### Explication sur `internalnorm` et ForwardDiff + +Le dรฉfaut `internalnorm = Common.real_norm` est conรงu pour supporter les nombres duaux ForwardDiff : + +- Pour des nombres rรฉels `u`, `real_norm(u, t)` retourne `abs(u)` (scalaire) ou `norm(u)` (vecteur). +- Pour des nombres duaux `ForwardDiff.Dual`, `real_norm(u, t)` appelle dโ€™abord `deepvalue(u)` qui extrait rรฉcursivement la valeur primale (Float64) du dual, puis calcule la norme sur cette valeur primale. + +Cela garantit que le contrรดle de pas adaptatif du solveur SciML utilise uniquement la partie primale pour รฉvaluer les erreurs, ce qui prรฉserve lโ€™invariance de grille (IND) lors de la diffรฉrenciation automatique avec ForwardDiff. Sans ce comportement, les dรฉrivรฉes seraient incluses dans le calcul de la norme, ce qui pourrait entraรฎner des grilles de pas diffรฉrentes pour des valeurs proches en primale mais diffรฉrentes en dรฉrivรฉe. + +### Exemples dโ€™utilisation + +```julia +flow = CTFlows.Flow( + vf_or_hvf_or_fun_or_prob; + alg=Tsit5(), # choix de l'algorithme SciML + abstol=1e-11, # tolรฉrance absolue (dรฉfaut: 1e-8) + internalnorm=(u, t) -> norm(u), # norme interne custom (dรฉfaut: Common.real_norm) +) +``` + +En pratique, pour un collรจgue qui veut juste tester: + +1. construire un `flow` via `CTFlows.Flow(...)`; +2. appeler en mode point (`t0, x0[, p0], tf`) ou trajectoire (`(t0, tf), x0[, p0]`) selon le type de flot; +3. si c'est un `SciMLProblemFlow`, utiliser soit l'appel sans argument, soit l'appel remake point. diff --git a/reports/save/notes.md b/reports/save/notes.md new file mode 100644 index 00000000..1b780268 --- /dev/null +++ b/reports/save/notes.md @@ -0,0 +1,4 @@ +# Notes + +- permettre de fournir la dimension de la variable en plus de la dimension de l'รฉtat pour un systรจme hamiltonien +- gรฉrer scalaire versus vecteur : construire le rhs en mode lazy avec le x0, p0 en argument pour avoir exactement le type de dรฉpart et faire en sorte que l'on fournisse exactement ce qu'il faut ร  hvf \ No newline at end of file diff --git a/reports/save/v0/VectorField.md b/reports/save/v0/VectorField.md new file mode 100644 index 00000000..f80e6c26 --- /dev/null +++ b/reports/save/v0/VectorField.md @@ -0,0 +1,163 @@ +# Flow from a vector field + +## Goal + +Define a flow from a vector field by going through the full CTFlows pipeline (`build_system` โ†’ `build_flow` โ†’ `integrate` โ†’ `solve`). A vector field is a function returning the derivative of the state, declined along two trait axes โ€” autonomous vs. non-autonomous (`f(x)` vs. `f(t, x)`) and fixed vs. non-fixed (with an extra variable `v`: `f(t, x, v)`) โ€” and along the shape of the state (scalar, vector, matrix). On top of this we want a configurable integrator (default `Tsit5`, switchable, with CPU/GPU paths), a sensible option set, a solution wrapper with getters and plotting, support for system/flow concatenation, and a correct treatment of dual numbers in the integrator's internal norm (issue [#93](https://github.com/control-toolbox/CTFlows.jl/issues/93)). + +This is delivered in several phases. Phase 1 establishes the end-to-end skeleton with the smallest viable surface; later phases progressively cover the rest. + +## Phase 1 โ€” In scope + +The first PR (branch `feature/vector-field-flow`) delivers an end-to-end vector-field flow with: + +- **`VectorField` type** with the full `{Autonomous, NonAutonomous} ร— {Fixed, NonFixed}` trait matrix, ported and trimmed from `save/src/types.jl#L626-L692`. State is scalar or vector (matrix-valued fields deferred). +- **`VectorFieldSystem <: Systems.AbstractSystem`** โ€” concrete system wrapping a `VectorField`. Implements `rhs!`, `dimensions`, `build_solution` (identity passthrough for now) and a new optional `Systems.ode_problem` contract method used by SciML-based integrators. +- **`VectorFieldModeler <: Modelers.AbstractFlowModeler`** โ€” trivial modeler whose business callable returns a `VectorFieldSystem`. Full CTSolvers strategy contract (`id`, `metadata`, `options`). +- **`NoOpADBackend <: ADBackends.AbstractADBackend`** โ€” placeholder AD backend satisfying the strategy contract; not exercised by `VectorField` (no differentiation needed for a raw RHS). Real AD backends arrive with the Hamiltonian/OCP modelers. +- **`AbstractSciMLIntegrator <: Integrators.AbstractIntegrator`** in core; concrete **`SciMLIntegrator`** in a new package extension `CTFlowsSciML` built on `SciMLBase` + `OrdinaryDiffEqCore`. The user loads any algorithm package (`OrdinaryDiffEqTsit5`, `OrdinaryDiffEq`, `DifferentialEquations`) and passes `alg = Tsit5()`. +- **Pipeline conveniences**: `build_system(vf)` and `Flow(vf; alg, kwargs...)` so the user-facing idiom from `save/ext/vector_field.jl` is preserved. +- **Phase-1 integrator options** (subset of the original list, sanity-checked against SciML at implementation time): `alg`, `abstol`, `reltol`, `maxiters`, `dt`, `adaptive`, `save_everystep`, `saveat`. +- **Tests** following `.windsurf/rules/testing.md`: unit tests for each new type, contract tests for each strategy, an end-to-end pipeline test guarded by the SciML extension (using `OrdinaryDiffEqTsit5` in the test extras) integrating `f(t, x) = -x` and checking against the analytical solution. + +The detailed implementation plan lives in `vector-field-flow-019537.md`. + +## Deferred to later phases + +The following items from the original brief are intentionally **not** in Phase 1 and will be addressed in dedicated follow-ups: + +- **Matrix-valued vector fields**. Phase 1 supports scalar/vector states only. +- **Solution wrapper with getters**. Phase 1 returns the raw SciML `ODESolution`. Later, `build_solution` will produce a CTFlows-specific struct with `state(sol)`, `time_grid(sol)`, `sol(t)`, and friends. +- **Plot recipe**. Mirroring `CTModelsPlots` (see references below) โ€” deferred. +- **`internalnorm` option and ForwardDiff.Dual fix** (issue [#93](https://github.com/control-toolbox/CTFlows.jl/issues/93)). The reference snippet is preserved in the appendix as the design template. +- **Event/jump handling**: `tstops`, `callback`, `jumps` options. +- **GPU support and CPU/GPU strategy parameters** (ร  la `CTSolvers` `Modelers/exa.jl`). The Exa metadata snippet is preserved in the appendix as the design template. +- **Concatenation**: `MultiPhaseSystem`, `MultiPhaseFlow`, the `โˆ˜` and `*` operators. See `reports/design.md` ยง2.3โ€“ยง2.4. +- **Real AD backends** (ForwardDiff, Zygote, Enzyme). Only `NoOpADBackend` in Phase 1. +- **Per-algorithm integrator strategies** (e.g. dedicated `Tsit5Integrator`, GPU integrators). Phase 1 ships a single generic `SciMLIntegrator`. + +## Open questions / to clarify before Phase โ‰ฅ 2 + +These points are not yet decided and need a design pass before the corresponding feature is implemented: + +- **Matrix-field shape in the trait matrix**: add a third trait axis, parameterise by `T <: AbstractArray`, or handle it purely through method dispatch on `f`'s signature? +- **Option ownership** between modeler and integrator. The Phase-1 plan puts all SciML options on the integrator. Some options (e.g. `tstops`, `jumps`, `internalnorm`) arguably describe the *problem*, not the *solver* โ€” see `reports/design.md` ยง3.1. Revisit when adding event handling. +- **Integrator granularity**: keep one generic `SciMLIntegrator` long-term, or introduce one strategy per algorithm (`Tsit5Integrator`, `Rodas4Integrator`, โ€ฆ) sharing a common metadata base? +- **GPU pathway**: do we mirror `exa.jl` exactly with `CPU`/`GPU` parameters on `AbstractSciMLIntegrator{P}` and computed defaults, or carry the parameter on the AD backend instead, or both? See the Exa snippet in the appendix. +- **Where the dual-number `internalnorm` fix lives**: as a default in the integrator metadata (auto-on when state `eltype` is `ForwardDiff.Dual`), or as an explicit opt-in option the user activates? +- **Solution wrapper API**: introduce a CTFlows-specific type with `state(sol)`/`time_grid(sol)`, or just return the SciML `ODESolution` and document that `x(t) = sol(t)` is the canonical accessor? +- **Plot recipe location**: a CTFlows package extension on `RecipesBase`/`Plots`, or a sibling package mirroring `CTModelsPlots`? +- **SciML option-name verification**: every option name in the Phase-1 list must be checked against the actual `SciMLBase`/`OrdinaryDiffEq` APIs at implementation time (the original brief already flagged this). + +## Appendix โ€” reference snippets + +### Exa modeler with CPU/GPU parameter + +Kept verbatim from [`CTSolvers Modelers/exa.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Modelers/exa.jl) as the reference template for parameterising a CTFlows strategy by `CPU`/`GPU` (see [CTSolvers parameters](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/parameters.jl)). + +```julia +__exa_model_backend() = nothing +__exa_model_backend(::Type{CPU}) = nothing +function __exa_model_backend(P::Type{GPU}) + return __get_cuda_backend(P) +end +function __get_cuda_backend(::Type{<:GPU}) + throw( + Exceptions.ExtensionError( + :CUDA; + message="to use GPU backend with Exa modeler", + feature="GPU computation with ExaModels", + context="Load CUDA extension first: using CUDA", + ), + ) +end + +function Strategies.metadata(::Type{<:Modelers.Exa{P}}) where {P<:Union{CPU,GPU}} + return Strategies.StrategyMetadata( + # === Existing Options (enhanced) === + Strategies.OptionDefinition(; + name=:base_type, + type=DataType, + default=__exa_model_base_type(), + description="Base floating-point type used by ExaModels", + validator=validate_exa_base_type, + ), + Strategies.OptionDefinition(; + name=:backend, + type=Union{Nothing,KernelAbstractions.Backend}, # More permissive for various backend types + default=__exa_model_backend(P), + description="Execution backend for ExaModels (CPU, GPU, etc.)", + computed=true, # Default is computed from parameter P + aliases=(:exa_backend,), + validator=function (backend) + if !__consistent_backend(P, backend) + param_str = P == CPU ? "CPU" : "GPU" + backend_str = + backend === nothing ? "no backend" : string(typeof(backend)) + @warn "Inconsistent backend ($backend_str) for $param_str parameter" maxlog=1 + end + return backend + end, + ), + ) +end +``` + +### Dual-number internal norm + +Reference snippet for the future `internalnorm` fix (issue [#93](https://github.com/control-toolbox/CTFlows.jl/issues/93)) โ€” only the real part of `ForwardDiff.Dual` numbers should contribute to the adaptive-step norm. + +```julia +df_sol = DataFrame(adaptive=Bool[], VAR_IND=String[], internalnorm=String[], norm_โˆž_error=Real[], norm_โˆž_diff=Real[], time_steps=Vector[]) + + +# with my_norm the diagram switches +sse(x::Number) = x^2 +sse(x::ForwardDiff.Dual) = sse(ForwardDiff.value(x)) #+ sum(sse, ForwardDiff.partials(x)) +totallength(x::Number) = 1 +function totallength(x::ForwardDiff.Dual) + totallength(ForwardDiff.value(x)) #+ sum(totallength, ForwardDiff.partials(x)) +end +totallength(x::AbstractArray) = sum(totallength, x) +function my_norm(u, t) + return sqrt(sum(x -> sse(x), u) / totallength(u)) +end + +test_FD!(df_sol,fun_lin, tspan, x0, ฮป, sol_โˆ‚xO_flow,true,internalnorm=my_norm) +println(df_sol) +``` + +### Candidate option list (original brief) + +Full list of options envisioned across all phases (Phase 1 implements only the subset listed in the in-scope section above; the rest must still be checked against the actual SciML/OrdinaryDiffEq APIs): + +- `alg`: the integrator algorithm +- `abstol`: the absolute tolerance +- `reltol`: the relative tolerance +- `maxiters`: the maximum number of iterations +- `internalnorm`: the internal norm function +- `save_everystep`: whether to save every step +- `dt`: the time step +- `adaptive`: whether to use adaptive time stepping +- `tstops` +- `callback` +- `saveat` + +### References + +- [CTModels โ€” `Autonomous`/`NonAutonomous` types](https://github.com/control-toolbox/CTModels.jl/blob/34fe34d6f5e76d8b8d750475c96e5ef1e8fb8d3e/src/OCP/Types/components.jl#L28-L40) +- `save/src/types.jl#L38-L54` โ€” time dependence traits +- `save/src/types.jl#L62-L79` โ€” variable dependence traits +- `save/src/types.jl#L626-L692` โ€” original `VectorField` implementation +- `save/ext/vector_field.jl` โ€” original `Flow(::VectorField; ...)` extension +- [`Tsit5` documentation](https://docs.sciml.ai/OrdinaryDiffEq/stable/explicit/Tsit5/#OrdinaryDiffEqTsit5) +- [CTSolvers โ€” strategy parameters](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/parameters.jl) +- [CTSolvers โ€” Exa modeler with CPU/GPU](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Modelers/exa.jl) +- [CTFlows issue #93 โ€” dual-number internal norm](https://github.com/control-toolbox/CTFlows.jl/issues/93) +- CTModelsPlots extension (template for the future plot recipe): + - <https://github.com/control-toolbox/CTModels.jl/blob/main/ext/CTModelsPlots.jl> + - <https://github.com/control-toolbox/CTModels.jl/blob/main/ext/plot.jl> + - <https://github.com/control-toolbox/CTModels.jl/blob/main/ext/plot_default.jl> + - <https://github.com/control-toolbox/CTModels.jl/blob/main/ext/plot_utils.jl> +- `reports/design.md` โ€” overall CTFlows architecture (objects, strategies, pipelines) +- `reports/candidate_strategies.md` โ€” strategy candidate analysis +- `vector-field-flow-019537.md` โ€” Phase 1 implementation plan diff --git a/reports/save/v0/candidate_strategies.md b/reports/save/v0/candidate_strategies.md new file mode 100644 index 00000000..a658ef44 --- /dev/null +++ b/reports/save/v0/candidate_strategies.md @@ -0,0 +1,406 @@ +# Candidate Strategies for CTFlows + +This report is a **brainstorm** โ€” no decisions are made here. Every dimension of choice in +CTFlows that *could* become a strategy or strategy family is listed and discussed. Decisions on +which candidates to promote to actual families, and the drafting of their business contracts, +are deferred to a follow-up step. + +For background on the strategy contract and the infrastructure it unlocks, see +[`strategies.md`](strategies.md). + +## 1. Conceptual hierarchy and the CTSolvers analogy + +CTFlows maps onto the same three-layer architecture as CTSolvers: + +```text +CTSolvers CTFlows (proposed) +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +AbstractOptimizationProblem AbstractSystem + OCP, NLP, โ€ฆ VectorField, Hamiltonian, + OCP, Function, ODEProblem, โ€ฆ + +AbstractNLPModeler AbstractFlowModeler + OCP โ†’ NLP model input โ†’ AbstractSystem + (ADNLP, Exa) (OpenLoop, ClosedLoop, + HamiltonianModeler, โ€ฆ) + +AbstractNLPSolver AbstractIntegrator + NLP โ†’ execution stats ODEProblem โ†’ trajectory + (Ipopt, MadNLP, โ€ฆ) (Tsit5, DP8, Rodas4, โ€ฆ) +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +``` + +- **Layer 1 โ€” the object** (`AbstractSystem`): the thing being acted upon. Types vary by + problem structure; selection is handled by Julia's type dispatch, not by strategies. +- **Layer 2 โ€” the construction strategy** (`AbstractFlowModeler`): takes any integrable input + (a `VectorField`, a `Hamiltonian`, an OCP with control law, โ€ฆ) and produces a fully assembled + `AbstractSystem` with an embedded `rhs!`. Direct analog of `AbstractNLPModeler`. +- **Layer 3 โ€” the execution strategy** (`AbstractIntegrator`): solves the Cauchy problem and + returns a trajectory. Direct analog of `AbstractNLPSolver`. + +A multi-phase composition level (several systems + switching times) sits above as a +higher-order object built from flows, with no direct CTSolvers counterpart. + +The precise mapping of **actions**, **objects**, and **strategies** onto this hierarchy โ€” +including canonical names (`build`, `solve`, โ€ฆ) and user-friendly aliases (`Flow(...)`, +`f(t0, x0, p0, tf)`) โ€” is developed in ยง2. + +## 2. Actions, objects, and strategies in CTFlows + +Applying the action-object-strategies pattern from [`strategies.md` ยง2](strategies.md) to +CTFlows gives the following mapping. Each action has a **canonical name** consistent with +CTSolvers (`build`, `solve`, โ€ฆ) and, where appropriate, a **user-friendly alias** that matches +the CTFlows idiom (`Flow(...)`, `f(t0, x0, p0, tf)`). + +### 2.1 Action inventory + +| Action (canonical) | User-friendly alias | Level | Object | Strategies | Result | +| --- | --- | --- | --- | --- | --- | +| `build_system` | `System(ocp, u, modeler)` | atomic | `(ocp, control_law)` | `flow_modeler` | `AbstractSystem` | +| `build_flow` | `Flow(system, integrator)` | atomic | `system` | `ode_integrator` | `Flow` | +| `build_system` + `build_flow` | `Flow(ocp, u, modeler, integrator)` | pipeline | `(ocp, control_law)` | `(flow_modeler, ode_integrator)` | `Flow` | +| `integrate` | `f(t0, x0[, p0], tf)` | atomic | `flow` | `ode_integrator` (embedded) | trajectory | +| `solve` | `f((t0, tf), x0[, p0])` | pipeline | `flow` | โ€” (all embedded) | `CTModels.Solution` | +| `build_solution` | โ€” | atomic | `(system, ode_sol)` | โ€” (embedded in system) | `CTModels.Solution` | + +The key insight: a `Flow` is simply `(system, integrator)`. The system is a **fully assembled +object** โ€” it already embeds its `rhs!`, its metadata, and its solution-building logic. The +modeler does its work once, at `build_system` time, and disappears from subsequent calls. + +`build_system` applies to all input types. For raw types (`VectorField`, `Hamiltonian`, +`Function`, โ€ฆ) a default or trivial modeler assembles the system; for OCP inputs a +non-trivial modeler is required to handle the loop type and augmentation choices. + +### 2.2 The `build_system` atomic action + +`build_system` is the CTFlows analog of `build_model` in CTSolvers. The modeler's callable +produces a fully assembled `AbstractSystem`: + +```julia +# For raw inputs (VectorField, Hamiltonian, Function, โ€ฆ) +function build_system(input, modeler) + return modeler(input) # modeler(input) โ†’ AbstractSystem +end + +# For OCP inputs (control law embedded in the system) +function build_system(ocp, u, modeler) + return modeler(ocp, u) # modeler(ocp, u) โ†’ AbstractSystem +end +``` + +The resulting system embeds: + +- `rhs!(du, u, p, t)` โ€” the ODE right-hand side (built by the modeler), +- dimensional metadata (state, costate, control sizes), +- `build_solution(system, ode_sol)` โ€” how to package a trajectory into a result. + +### 2.3 The `build_flow` and `solve` actions + +With the system fully assembled, `build_flow` is just a wrapper: + +```julia +function build_flow(system::AbstractSystem, integrator::AbstractIntegrator) + return Flow(system, integrator) +end +``` + +The user-facing `Flow(ocp, u, modeler, integrator)` sequences both steps: + +```julia +# Canonical two-step form +flow = build_flow(build_system(ocp, u, modeler), integrator) + +# User-friendly pipeline alias (build_system + build_flow in one call) +flow = Flow(ocp, u, modeler, integrator) + +# Descriptive form (OptimalControl registry) +flow = Flow(ocp, u, :tsit5; abstol = 1e-10) +``` + +Calling the flow is `integrate`; for OCP flows `solve` adds `build_solution`: + +```julia +xf, pf = f(t0, x0, p0, tf) # integrate โ€” returns final state/costate + +function solve(flow::Flow, tspan, x0, p0) + ode_sol = flow(tspan, x0, p0) # integrate + return build_solution(flow.system, ode_sol) # system handles its own solution +end +``` + +### 2.4 Key consequences + +- **The modeler acts once, at construction.** `modeler(ocp, u)` โ†’ `AbstractSystem` is the + atomic call โ€” directly analogous to `modeler(ocp, x0)` โ†’ `NLP` in CTSolvers. After that, + the modeler is gone from the call chain. +- **The system is the stable intermediate object.** It has a contract โ€” `rhs!`, dimensions, + `build_solution` โ€” that `build`, `integrate`, and `solve` rely on. The loop type + (open/closed/dyn-closed) is encoded in the type of `u` and thus in the type of the system + produced; it does not appear in the modeler's contract. +- **`build_flow` is trivial.** It only binds a system to an integrator. The `Flow` carries no + extra logic โ€” complexity lives in the system (modeler's output) and the integrator. +- **`build_solution` needs no strategy argument.** The system already embeds its + solution-building logic; `build_solution(system, ode_sol)` is self-contained. +- **Canonical names** (`build_system`, `build_flow`, `integrate`, `solve`, `build_solution`) + follow the CTSolvers convention. **User-friendly names** (`Flow(...)`, + `f(t0, x0, p0, tf)`) are idiomatic CTFlows aliases on top. + +The following sections (ยง3โ€“ยง9) develop each candidate family in detail. + +## 3. System (not a strategy โ€” the problem type) + +`AbstractSystem` (or the existing type hierarchy) groups everything that can be integrated: + +- `VectorField` โ€” `f(t, x, v)` or `f(x)` โ†’ `แบ‹` +- `Hamiltonian` โ€” `H(t, x, p, v)` โ†’ scalar +- `HamiltonianVectorField` โ€” `(แบŠ, แน–)` already in phase space +- OCP with control (`WithControlModel`) โ€” dynamics + cost + control law +- `ControlFreeModel` โ€” OCP without control (parameter estimation, optimal design) +- Raw `Function` โ€” `(t, x, v) โ†’ แบ‹` with no additional structure +- `ODEProblem` / `ODEFunction` โ€” SciML standard form (already integrable) + +**This is not a strategy**: the types are not interchangeable implementations of the same role. +The right mechanism for varying the input type is Julia's type dispatch. What varies, for a +given input type, is *how* the system is assembled from it โ€” and that is the role of the flow +modeler in `build_system`. + +**`MultiPhaseSystem <: AbstractSystem`**: an ordered list of systems + switching conditions +assembled into a single composite system. Belongs at this level โ€” assembled by a flow modeler +and handed to `build_flow` like any other system. Compatibility (same OCP type, or matching +dimensions encoded in parametric types) is enforced at construction time. Uses one integrator +for a single ODE solve with `tstops` and callbacks handling the switching. + +## 4. Candidate family: Flow Modeler (`AbstractFlowModeler`) + +**Role**: takes any integrable input and produces a fully assembled `AbstractSystem` that +embeds its own `rhs!`, dimensional metadata, and solution-building logic. Called via +`build_system`. This is the direct analog of `AbstractNLPModeler`. + +**Current code**: this logic is currently hardcoded and spread across `hamiltonian.jl`, +`optimal_control_problem.jl`, `vector_field.jl`, and `function.jl`. There is no user-visible +choice here โ€” the construction is determined entirely by the system type. + +**Why a strategy**: for several input types, there are genuinely different ways to assemble +the system: + +- **For OCP with control** โ€” this is where the *loop type* lives: + - `OpenLoopModeler` โ€” control `u(t, v)` is a pre-computed open-loop law. + - `ClosedLoopModeler` โ€” control `u(t, x, v)` is a state-feedback law. + - `DynClosedLoopModeler` โ€” control `u(t, x, p, v)` is a state-costate feedback law + (Hamiltonian gradient feedback). This is the natural form in indirect optimal control. + - Each modeler wraps the user-provided function differently and assembles a different system. + - Common option: `augmented::Bool` โ€” whether to extend the system with + $dp_v/dt = -\partial H/\partial v$ (needed to return the dual variable when it exists; + roadmap item [issue #103](https://github.com/control-toolbox/CTFlows.jl/issues/103)). + +- **For `Hamiltonian`** (without OCP structure): + - `HamiltonianModeler` โ€” standard Hamilton's equations `(แบ‹, แน—) = (โˆ‚H/โˆ‚p, -โˆ‚H/โˆ‚x)`. + - `AugmentedHamiltonianModeler` โ€” same, extended with the $p_v$ costate equation. + - This maps directly to `rhs_augmented` in the current `ext/hamiltonian.jl`. + +- **For `ControlFreeModel`**: + - `ControlFreeModeler` โ€” Pontryagin Hamiltonian with empty control; augmentation optional. + +- **For `VectorField` / `Function`**: + - A trivial modeler (`VectorFieldModeler`) that assembles the function as an + `AbstractSystem`. Conceptually an identity, but preserves the uniform `build_system` + interface across all input types. + +- **For `ODEProblem` / `ODEFunction`**: + - A passthrough modeler โ€” the system is already in ODE form, just forward it. + +**Canonical callable**: `(modeler)(input[, control_law]) โ†’ AbstractSystem` โ€” the modeler +takes whatever input is relevant and produces a fully assembled system with embedded `rhs!` +and solution-building logic. + +**Candidate options**: `augmented::Bool`, `internalnorm`, `tstops`, `jumps`. + +**Open questions**: + +- Single `AbstractFlowModeler` with dispatch on system type, or per-system-type + sub-families (`AbstractOCPFlowModeler`, `AbstractHamiltonianFlowModeler`, โ€ฆ)? + The per-system split is more ISP-compliant but requires more families. +- Should `internalnorm`, `tstops`, `jumps` live on the modeler (they describe the ODE + structure) or on the integrator (they configure the solver)? Currently they are passed + to the solver. +- Is `ControlFreeModeler` a sub-case of `OpenLoopModeler` (with `u = []`) or a separate + strategy? + +**Classification**: ๐ŸŸข **Strong candidate.** This is the most important gap in the current +architecture. Without it, all construction choices are hardcoded and non-extensible. + +## 5. Candidate family: ODE Integrator Backend (`AbstractIntegrator`) + +**Role**: solves a Cauchy problem (an `ODEProblem` or equivalent `rhs!` + initial condition + +time span) and returns a trajectory. Direct analog of `AbstractNLPSolver`. + +**Current code**: `__alg() = Tsit5()`, `__abstol() = 1e-10`, `__reltol() = 1e-10`, +`__saveat() = []`, `__internalnorm()` in `ext_default.jl`; threaded through all `Flow(...)` +constructors as flat kwargs. The entire extension `CTFlowsODE` loads `OrdinaryDiffEq`. + +**Why a strategy**: different algorithms live in different packages +(`OrdinaryDiffEqTsit5.jl`, `OrdinaryDiffEqRosenbrock.jl`, โ€ฆ). The roadmap explicitly calls +for `using`-based backend switching with `Tsit5` as default. Each algorithm has its own +relevant options (step size control, stiffness detection, dense output, โ€ฆ). GPU integrators +(`DiffEqGPU`) require different defaults and a different loading path. + +**Candidate strategies**: `Tsit5`, `BS3`, `DP8`, `Rodas4`, `AutoTsit5`, `KenCarp4`, and one +strategy per relevant algorithm in the OrdinaryDiffEq ecosystem. + +**Candidate options**: `abstol`, `reltol`, `saveat`, `internalnorm`, `dense`, `maxiters`. + +**Parameters**: `CPU`, `GPU` (GPU integrators expose different defaults, e.g. larger +`maxiters`; dispatch at compile time). + +**Canonical callable**: `(integrator)(ode_problem; display) โ†’ trajectory` (mirrors +`solver(nlp; display) โ†’ stats` in CTSolvers). + +**Tag dispatch pattern**: as in CTSolvers solvers, the type can be declared in the main +package while the implementation lives in a package extension, loaded only when the +corresponding `OrdinaryDiffEqXxx.jl` is brought in. + +**Open questions**: is one strategy per ODE algorithm the right granularity, or should +stiffness class (non-stiff, stiff, DAE) be the primary axis with algorithm as a sub-option? +Should tolerances be options on the integrator or passed separately at call time? + +**Classification**: ๐ŸŸข **Strong candidate.** The most natural extension point in CTFlows. + +## 6. Candidate family: AD Backend (`AbstractADBackend`) + +**Role**: provides automatic differentiation for `ctgradient`, `ctderivative`, `ctjacobian` +used throughout differential geometry operations and flow construction. + +**Current code**: `utils.jl` calls `ForwardDiff.derivative`, `ForwardDiff.gradient`, +`ForwardDiff.jacobian` directly โ€” hardcoded, not configurable. + +**Why a strategy**: GPU support (roadmap item) requires a GPU-compatible AD backend (Zygote, +Enzyme). Reverse-mode AD is more efficient for large-state systems. This is a cross-cutting +concern: it affects both differential geometry operators (`Lift`, `ad`, `@Lie`) and the +construction of the Hamiltonian gradient in `rhs_augmented`. + +**Candidate strategies**: `ForwardDiffAD`, `ZygoteAD`, `EnzymeAD`, `ReverseDiffAD`. + +**Parameters**: `CPU`, `GPU`. + +**Canonical callable**: `ctgradient(backend, f, x)`, `ctjacobian(backend, f, x)`, etc. + +**Open questions**: is this one family (uniform AD interface) or two (one for scalar +derivatives, one for jacobians/gradients)? Should the AD backend be tied to the integrator +(so `Tsit5(GPU)` implies `EnzymeAD(GPU)`) or kept independent? Is this a CTFlows family or +a CTBase concern? + +**Classification**: ๐ŸŸข **Strong candidate** for GPU support; may be cross-package. + +## 7. Candidate family: Solution Builder (`AbstractFlowSolutionBuilder`) + +**Role**: packages the raw ODE trajectory of an OCP flow into a `CTModels.Solution` +(cost evaluation, trajectory extraction, control reconstruction). + +**Current code**: `CTModels.Solution(ocfs::OptimalControlFlowSolution; kwargs...)` in +`ext_types.jl` โ€” integrates the Lagrange cost with a second nested ODE solve, extracts +`x(t)`, `u(t)`, `p(t)`, builds a full `CTModels.Solution`. + +**Why a strategy**: one may want a lightweight solution (no cost integration, no Lagrange +term evaluation) for performance-critical applications; a GPU variant may need a different +packaging path. The analogy is the "solution builder callable" in `AbstractNLPModeler` +(`(modeler)(prob, nlp_stats) โ†’ Solution`). + +**Candidate strategies**: `FullOCPSolutionBuilder` (current behavior), +`LightweightOCPSolutionBuilder` (skip Lagrange integration). + +**Candidate options**: `compute_objective::Bool`, `control_interpolation::Symbol`. + +**Open questions**: should this be a separate family or an option on the flow modeler (similar +to how the NLP modeler bundles both model building and solution building)? Keeping them +together makes the analogy with CTSolvers cleaner. + +**Classification**: ๐ŸŸก **Arguable.** Strong if bundled with the flow modeler (as in CTSolvers); +weaker if separated. + +## 8. Multi-phase composition: two levels + +Multi-phase composition exists at two distinct levels with different semantics. + +### 8.1 System level โ€” `MultiPhaseSystem` + +**What it is**: an ordered list of systems + switching conditions assembled via the `โˆ˜` +operator (or `MultiPhaseSystem([sys1, sys2], [t_switch])`). The result is an +`AbstractSystem` and is handled by the rest of the pipeline unchanged: + +```julia +multi_sys = sys1 โˆ˜ sys2 # MultiPhaseSystem +flow = build_flow(multi_sys, integrator) # single ODE solve +xf, pf = flow(t0, x0, p0, tf) # one call +``` + +**Semantics**: ONE integration over `[t0, tf]`; the integrator uses `tstops` and a callback +to handle state discontinuities at switching times. + +**Compatibility**: enforced at `โˆ˜` construction time โ€” same OCP type (for OCP systems), or +matching state dimension encoded in the parametric type `HamiltonianSystem{n}`. + +**Classification**: โŒ **Not a strategy.** `MultiPhaseSystem` is an `AbstractSystem` object; +its construction is handled by the flow modeler (same as any other system type). + +### 8.2 Flow level โ€” `MultiPhaseFlow` + +**What it is**: concatenation of already-built flows via the `*` operator: + +```julia +flow1 = build_flow(sys1, tsit5_integrator) +flow2 = build_flow(sys2, rodas4_integrator) +multi = flow1 * flow2 # MultiPhaseFlow +xf = multi(t0, x0, p0, tf) # sequential calls +``` + +**Semantics**: SEQUENTIAL integration โ€” `flow1` is called from `t0` to `t_switch`, its +output is passed as initial condition to `flow2` from `t_switch` to `tf`. Each phase uses +its own integrator with its own tolerances. + +**Compatibility**: state dimensions must match between consecutive phases; checked at `*` +construction. No integrator compatibility required โ€” each phase integrates independently. + +**Current code**: `concatenation.jl` already implements this `*` operator for two flows. + +**Classification**: โŒ **Not a strategy.** `MultiPhaseFlow` is a composite callable object, +not an interchangeable implementation of a role. The `*` operator is a builder utility. + +## 9. What is probably not a strategy + +- **Flow problem type** (`VectorField`, `Hamiltonian`, OCP, `Function`, `ODEProblem`): distinct + types with different interfaces, not interchangeable implementations of the same role. The + right mechanism is Julia's multiple dispatch, not strategy lookup. +- **Event and jump handling** (`tstops`, `jumps`, callbacks): these configure *when* the + integrator pauses or applies discrete effects. Best modeled as options on the integrator + strategy or passed at call time, not as a separate family. +- **`autonomous` / `variable` flags**: derived from the OCP type at construction time, not a + user strategy choice. +- **Control law function** (the Julia function `u(t, x, p, v)` provided by the user): this is + user data, not a strategy. The *type* of law (open/closed/dyn-closed) is what becomes a + strategy (via the flow modeler). + +## 10. Summary + +| Candidate | Tier | Action(s) | CTSolvers analog | Note | +| --- | --- | --- | --- | --- | +| **Flow Modeler** | ๐ŸŸข Strong | `build_system` | `AbstractNLPModeler` | Central gap; loop type + augmentation | +| **ODE Integrator Backend** | ๐ŸŸข Strong | `build_flow`, `integrate` | `AbstractNLPSolver` | Roadmap priority, extension-based | +| **AD Backend** | ๐ŸŸข Strong | `ctgradient`, `ctjacobian` | โ€” (cross-cutting) | Required for GPU | +| **Solution Builder** | ๐ŸŸก Arguable | `build_solution` | Modeler solution callable | Natural if bundled with modeler | +| **MultiPhaseSystem** | โŒ Not a strategy | `build_system` (via `โˆ˜`) | โ€” | `AbstractSystem` object; compatibility at type level | +| **MultiPhaseFlow** | โŒ Not a strategy | `*` operator | โ€” | Composite callable; sequential integration, per-phase integrators | +| **Flow Problem Type** | โŒ Not a strategy | โ€” | The "problem" itself | Type dispatch is correct | +| **Event/jump handling** | โŒ Not a strategy | โ€” | Solver kwargs | Options on integrator | + +## 11. Next step + +Decisions on which candidates to retain as actual families, and the drafting of their +**business contracts** (required methods, callable signatures, options), are the subject of +the next report. + +Items to decide: + +- Whether `AbstractFlowModeler` is one family or several (per-system sub-families). +- Whether the solution builder is bundled with the flow modeler or separate. +- Whether the AD backend is a CTFlows family or delegated to a lower-level package. +- The granularity of the ODE integrator family (per algorithm vs. per stiffness class). diff --git a/reports/save/v0/design.md b/reports/save/v0/design.md new file mode 100644 index 00000000..7b650ec2 --- /dev/null +++ b/reports/save/v0/design.md @@ -0,0 +1,402 @@ +# CTFlows Design: Contracts and Pipelines + +This document specifies the abstract types, strategy family contracts, and pipeline functions +of CTFlows in enough detail to write tests with fake concrete types. It is the operational +counterpart of [`candidate_strategies.md`](candidate_strategies.md) (which lists candidates +and rationale) and [`strategies.md`](strategies.md) (which describes the CTSolvers strategy +contract on which this design rests). + +## 1. Overview + +CTFlows organises its code along three concerns: + +- **Objects** โ€” `AbstractSystem` and `AbstractFlow` (with their multi-phase variants). They + are *what* is acted upon. Not strategies. +- **Strategy families** โ€” `AbstractFlowModeler`, `AbstractIntegrator`, `AbstractADBackend`. + Each family is `<: CTSolvers.Strategies.AbstractStrategy` and inherits the full CTSolvers + contract (`id`, `metadata`, `options`, `Base.show`, `describe`, โ€ฆ). +- **Actions / pipelines** โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, + `solve`. They are written **on the abstract types** so that a concrete implementation + (real or fake) plugs in without changing the pipeline. + +Every required method has a default implementation that throws +`CTBase.Exceptions.NotImplemented` with a descriptive message and a suggestion โ€” this is the +mechanism that *forces* every concrete type to honour its contract, exactly as in +[`abstract_strategy.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/abstract_strategy.jl). + +The pseudo-code below is illustrative; the actual Julia source will live in `src/`. + +## 2. Non-strategy types (objects) + +These are pure objects with their own contract. They do **not** inherit from +`AbstractStrategy`, so `id`/`metadata`/`options` do not apply, and `Base.show` must be +defined explicitly using the same tree-style formatting as CTSolvers strategies (cf. +[`abstract_strategy.jl#L299-L349`](https://github.com/control-toolbox/CTSolvers.jl/blob/ff6e57d3dd598a4143b8f2bf0a85d5fa4c264c92/src/Strategies/contract/abstract_strategy.jl#L299-L349)). + +### 2.1 `AbstractSystem` + +The fully assembled object that can be integrated. It embeds its own `rhs!`, dimensional +metadata, and solution-building logic โ€” assembled once at `build_system` time. + +```julia +abstract type AbstractSystem end +``` + +**Required methods** (NotImplemented defaults): + +```julia +function rhs!(system::AbstractSystem) + throw(NotImplemented( + "AbstractSystem rhs! method not implemented"; + required_method = "rhs!(system::$(typeof(system)))", + suggestion = "Return a function (du, u, p, t) -> nothing that fills du in place.", + context = "AbstractSystem.rhs! - required method implementation", + )) +end + +function dimensions(system::AbstractSystem) + throw(NotImplemented( + "AbstractSystem dimensions method not implemented"; + required_method = "dimensions(system::$(typeof(system)))", + suggestion = "Return a NamedTuple, e.g. (n_x=n, n_p=n, n_u=m, n_v=k).", + context = "AbstractSystem.dimensions - required method implementation", + )) +end + +function build_solution(system::AbstractSystem, ode_sol) + throw(NotImplemented( + "AbstractSystem build_solution method not implemented"; + required_method = "build_solution(system::$(typeof(system)), ode_sol)", + suggestion = "Package the raw ODE trajectory into the appropriate result (raw trajectory or CTModels.Solution).", + context = "AbstractSystem.build_solution - required method implementation", + )) +end +``` + +**Contract semantics**: + +- `rhs!` returns a closure that captures whatever the system needs (parameters, control law, + AD backend results, โ€ฆ). Once obtained, the system has no further dependency on the modeler. +- `dimensions` is the canonical introspection point for state/costate/control/variable sizes; + it is what the integrator and the multi-phase composition use to verify compatibility. +- `build_solution` is the post-processing step. For raw systems (vector field) it returns + the trajectory as-is; for OCP systems it integrates the Lagrange cost, reconstructs the + control, and returns a `CTModels.Solution`. + +**`Base.show`**: + +- `Base.show(io, ::MIME"text/plain", system)` โ€” tree-style: type name โ†’ `id` (none, since + not a strategy) โ†’ `dimensions` fields, one per line. +- `Base.show(io, system)` โ€” compact: `TypeName(n_x=โ€ฆ, n_p=โ€ฆ, โ€ฆ)`. + +### 2.2 `AbstractFlow` + +A callable object that combines an `AbstractSystem` with an `AbstractIntegrator`. It +carries no business logic of its own โ€” its job is to expose the integration protocol. + +```julia +abstract type AbstractFlow end +``` + +**Required methods** (NotImplemented defaults): + +```julia +function (flow::AbstractFlow)(t0, x0, tf) + throw(NotImplemented(...; required_method = "(flow::$(typeof(flow)))(t0, x0, tf)")) +end + +function (flow::AbstractFlow)(t0, x0, p0, tf) + throw(NotImplemented(...; required_method = "(flow::$(typeof(flow)))(t0, x0, p0, tf)")) +end + +function system(flow::AbstractFlow) + throw(NotImplemented(...; required_method = "system(flow::$(typeof(flow)))")) +end + +function integrator(flow::AbstractFlow) + throw(NotImplemented(...; required_method = "integrator(flow::$(typeof(flow)))")) +end +``` + +**Concrete `Flow <: AbstractFlow`** (provided by CTFlows): + +```julia +struct Flow{S<:AbstractSystem, I<:AbstractIntegrator} <: AbstractFlow + system::S + integrator::I +end + +system(f::Flow) = f.system +integrator(f::Flow) = f.integrator + +function (f::Flow)(t0, x0, tf) + # build ODEProblem from rhs!(f.system), tspan = (t0, tf), initial = x0 + # call f.integrator(prob, (t0, tf)) + # return f.system.build_solution(ode_sol) # or raw trajectory depending on system +end +``` + +**`Base.show`**: + +- Plain text: tree-style with `system` (compact form) and `integrator` (its `id`) as children. +- Compact: `Flow(system=โ€ฆ, integrator=โ€ฆ)`. + +### 2.3 `MultiPhaseSystem <: AbstractSystem` + +An ordered list of systems plus switching conditions, assembled into one composite +`AbstractSystem`. Inherits the full `AbstractSystem` contract โ€” `rhs!`, `dimensions`, and +`build_solution` are all defined and dispatch internally to the relevant phase. + +**Additional required methods** (NotImplemented defaults): + +```julia +function phases(system::MultiPhaseSystem) + throw(NotImplemented(...; required_method = "phases(system::$(typeof(system)))")) +end + +function switching(system::MultiPhaseSystem) + throw(NotImplemented(...; required_method = "switching(system::$(typeof(system)))")) +end +``` + +`phases` returns `Vector{<:AbstractSystem}`; `switching` returns the times or conditions +between consecutive phases. Compatibility (same OCP type, matching dimensions) is checked at +construction. + +**Operator**: `sys1 โˆ˜ sys2` builds a `MultiPhaseSystem` from compatible systems. + +### 2.4 `MultiPhaseFlow <: AbstractFlow` + +Concatenation of complete flows. Each phase keeps its own integrator; phases run sequentially. + +**Additional required methods** (NotImplemented defaults): + +```julia +function phases(flow::MultiPhaseFlow) + throw(NotImplemented(...; required_method = "phases(flow::$(typeof(flow)))")) +end + +function switching(flow::MultiPhaseFlow) + throw(NotImplemented(...; required_method = "switching(flow::$(typeof(flow)))")) +end +``` + +**Operator**: `flow1 * flow2` builds a `MultiPhaseFlow`. Compatibility check: state +dimension out of `flow1` must equal state dimension in of `flow2`. + +## 3. Strategy families (full CTSolvers contract) + +Each family is `<: CTSolvers.Strategies.AbstractStrategy` and therefore inherits **for free**: + +- `id(::Type{<:S}) โ†’ Symbol` โ€” NotImplemented default from CTSolvers +- `metadata(::Type{<:S}) โ†’ StrategyMetadata` โ€” NotImplemented default from CTSolvers +- `options(s::S) โ†’ StrategyOptions` โ€” default reads `s.options` field +- `Base.show(io, ::MIME"text/plain", s)` and `Base.show(io, s)` โ€” tree + compact, automatic +- `describe(::Type{<:S})` โ€” automatic, prints id/hierarchy/metadata + +Concrete strategy types must: + +1. Have an `options::StrategyOptions` field +2. Implement `id(::Type{<:S})` and `metadata(::Type{<:S})` +3. Provide a constructor `S(; mode=:strict, kwargs...)` that calls `build_strategy_options` +4. Implement the family-specific **business callable** (see below) + +### 3.1 `AbstractFlowModeler <: AbstractStrategy` + +**Role**: assemble an `AbstractSystem` from a single input. + +```julia +abstract type AbstractFlowModeler <: AbstractStrategy end +``` + +**Business callable** (NotImplemented default): + +```julia +function (modeler::AbstractFlowModeler)(input) + throw(NotImplemented( + "AbstractFlowModeler callable not implemented"; + required_method = "(modeler::$(typeof(modeler)))(input)", + suggestion = "Implement (m::YourModeler)(input) returning an AbstractSystem. " * + "For OCP modelers, accept input as a tuple (ocp, u).", + context = "AbstractFlowModeler call - required method implementation", + )) +end +``` + +**Note on input typing**: the fallback intentionally leaves `input` unqualified so concrete +modelers can dispatch freely. `OpenLoopModeler` will dispatch on `Tuple{<:OCP, <:Function}`, +`HamiltonianModeler` on `<:Hamiltonian`, etc. There is no overload `(m)(ocp, u)` โ€” the +unified signature `(m)(input)` is the only one. + +**Candidate option specs** (for `metadata`): `augmented::Bool`, `internalnorm`, `tstops`, +`jumps`. Concrete modelers add their own. + +### 3.2 `AbstractIntegrator <: AbstractStrategy` + +**Role**: solve a Cauchy problem. + +```julia +abstract type AbstractIntegrator <: AbstractStrategy end +``` + +**Business callable** (NotImplemented default): + +```julia +function (integrator::AbstractIntegrator)(ode_problem, tspan) + throw(NotImplemented( + "AbstractIntegrator callable not implemented"; + required_method = "(integrator::$(typeof(integrator)))(ode_problem, tspan)", + suggestion = "Implement (i::YourIntegrator)(prob, tspan) returning an ODE solution.", + context = "AbstractIntegrator call - required method implementation", + )) +end +``` + +**Candidate option specs**: `abstol`, `reltol`, `saveat`, `dense`, `maxiters`, +`internalnorm`. + +**Parameters** (in CTSolvers' parametric sense): `CPU`, `GPU` โ€” different defaults at compile +time. + +### 3.3 `AbstractADBackend <: AbstractStrategy` + +**Role**: provide gradient and Jacobian capabilities used by flow modelers when assembling a +system from an OCP (e.g. computing $\partial H/\partial p$, $\partial H/\partial x$). + +```julia +abstract type AbstractADBackend <: AbstractStrategy end +``` + +**Business callables** (NotImplemented defaults): + +```julia +function ctgradient(backend::AbstractADBackend, f, x) + throw(NotImplemented(...; required_method = "ctgradient(backend::$(typeof(backend)), f, x)")) +end + +function ctjacobian(backend::AbstractADBackend, f, x) + throw(NotImplemented(...; required_method = "ctjacobian(backend::$(typeof(backend)), f, x)")) +end +``` + +**Where it plugs in**: passed as a **separate argument** to `build_system` (see ยง4.1). The +modeler receives it and uses it internally to differentiate the user-provided functions. + +**Candidate option specs**: `chunk_size`, `tag`. Concrete backends add their own. + +## 4. Pipelines on abstract objects + +All pipelines below are written using only the abstract types declared in ยง2 and ยง3. They +are the level at which tests are written: provide a fake concrete type implementing the +required methods, run the pipeline, check the result. + +### 4.1 `build_system` + +```julia +function build_system(input, + modeler::AbstractFlowModeler, + ad_backend::AbstractADBackend) + return modeler(input, ad_backend) # delegate to modeler's business callable +end +``` + +The modeler receives both the input and the AD backend; what it does with the backend is its +own concern (the contract only forces it to return an `AbstractSystem`). + +> **Note**: a default AD backend can be provided so that `build_system(input, modeler)` is a +> shorter form for the common case. + +### 4.2 `build_flow` + +Atomic form (no modeler involved): + +```julia +function build_flow(system::AbstractSystem, integrator::AbstractIntegrator) + return Flow(system, integrator) +end +``` + +Pipeline alias (combines `build_system` and `build_flow`): + +```julia +function build_flow(input, + modeler::AbstractFlowModeler, + integrator::AbstractIntegrator, + ad_backend::AbstractADBackend) + system = build_system(input, modeler, ad_backend) + return build_flow(system, integrator) +end +``` + +### 4.3 `integrate` + +```julia +integrate(flow::AbstractFlow, t0, x0, tf) = flow(t0, x0, tf) +integrate(flow::AbstractFlow, t0, x0, p0, tf) = flow(t0, x0, p0, tf) +``` + +Thin wrapper over the callable protocol of `AbstractFlow`. Useful when one wants to spell +out the action by name rather than calling the flow directly. + +### 4.4 `build_solution` + +```julia +function build_solution(system::AbstractSystem, ode_sol) + # Delegates to the system's own build_solution (embedded by the modeler at + # build_system time). Required method on AbstractSystem (ยง2.1). + return _embedded_build_solution(system, ode_sol) +end +``` + +No strategy argument: the packaging logic was embedded by the modeler when the system was +assembled. This is the consequence of choosing a *fully assembled* system in ยง2.1. + +### 4.5 `solve` + +```julia +function solve(flow::AbstractFlow, tspan, x0) + t0, tf = tspan + ode_sol = integrate(flow, t0, x0, tf) + return build_solution(system(flow), ode_sol) +end + +function solve(flow::AbstractFlow, tspan, x0, p0) + t0, tf = tspan + ode_sol = integrate(flow, t0, x0, p0, tf) + return build_solution(system(flow), ode_sol) +end +``` + +Two-step pipeline: integrate, then package. No additional strategy โ€” both the integrator +(in the flow) and the solution builder (in the system) are already in place. + +## 5. Summary + +| Type | Kind | Required methods | `Base.show` | +|--------------------------|--------------|--------------------------------------------------------|-------------| +| `AbstractSystem` | object | `rhs!`, `dimensions`, `build_solution` | define | +| `AbstractFlow` | object | `(flow)(t0, x0[, p0], tf)`, `system`, `integrator` | define | +| `MultiPhaseSystem` | object | inherits + `phases`, `switching` | define | +| `MultiPhaseFlow` | object | inherits + `phases`, `switching` | define | +| `AbstractFlowModeler` | strategy | CTSolvers contract + `(modeler)(input)` | inherited | +| `AbstractIntegrator` | strategy | CTSolvers contract + `(integrator)(prob, tspan)` | inherited | +| `AbstractADBackend` | strategy | CTSolvers contract + `ctgradient`, `ctjacobian` | inherited | + +Pipeline functions on abstract types: + +- `build_system(input, modeler, ad_backend) โ†’ AbstractSystem` +- `build_flow(system, integrator) โ†’ Flow` (and pipeline alias) +- `integrate(flow, t0, x0[, p0], tf) โ†’ trajectory` +- `build_solution(system, ode_sol) โ†’ Any` +- `solve(flow, tspan, x0[, p0]) โ†’ Any` + +## 6. Next step + +Use this specification to: + +1. Define fake concrete types implementing each contract (one per abstract type). +2. Write tests that run each pipeline on the fakes and verify the calling convention, + delegation order, and `NotImplemented` paths. +3. Once the test suite is green, replace the fakes incrementally with real implementations + (`OpenLoopModeler`, `Tsit5Integrator`, `ForwardDiffBackend`, โ€ฆ) without changing the + pipelines. diff --git a/reports/save/v0/strategies.md b/reports/save/v0/strategies.md new file mode 100644 index 00000000..f40d7d07 --- /dev/null +++ b/reports/save/v0/strategies.md @@ -0,0 +1,356 @@ +# Strategies โ€” Conceptual Overview + +This report introduces the notion of *strategy* as defined in [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl) and used as a design pattern across the Control Toolbox. It is the first step of the CTFlows refactoring described in [`roadmap.md`](roadmap.md): before deciding which strategies and families CTFlows should expose, we need a shared vocabulary and a clear understanding of the contract. + +The CTFlows-specific mapping (integrators, loop encapsulations, flow modelers, differential-geometry operators, multi-phase concatenation, etc.) is **deliberately out of scope here** and will be the subject of a follow-up report. + +## 1. What is a strategy? + +A **strategy** is a typed, configured *descriptor* โ€” not an algorithm. Concretely, it is a small Julia struct that: + +- carries a **unique identifier** (a `Symbol`, e.g. `:ipopt`, `:collocation`), +- transports a set of **validated options** with their **provenance** (`:user` vs `:default`), +- serves as a **dispatch handle** for functional entry points such as `solve`, `build_model`, `build_solution`. + +The actual algorithmic work happens in methods that dispatch on the strategy's type. The strategy itself is just a configured choice. + +Every concrete strategy struct has a single field: + +```julia +struct MyStrategy <: SomeFamily + options::Strategies.StrategyOptions +end +``` + +## 2. The action-object-strategies pattern + +A strategy is useful only through the **actions** it parameterizes. The fundamental pattern is: + +```julia +action(object, strategies...) โ†’ result +``` + +- The **object** is the thing being acted upon โ€” a problem, a system, a model. +- The **strategies** are the configured descriptors that control *how* the action is carried out. +- The **result** is a new object: a solution, a flow, a trajectory, โ€ฆ + +### 2.1 Concrete example: `solve` in CTSolvers + +The high-level `solve` in CTSolvers follows this pattern exactly: + +```julia +function CommonSolve.solve( + problem::Optimization.AbstractOptimizationProblem, + initial_guess, + modeler::Modelers.AbstractNLPModeler, + solver::AbstractNLPSolver; + display::Bool = __display(), +) + # Build NLP model + nlp = Optimization.build_model(problem, initial_guess, modeler) + + # Solve NLP + nlp_solution = CommonSolve.solve(nlp, solver; display = display) + + # Build OCP solution + solution = Optimization.build_solution(problem, nlp_solution, modeler) + + return solution +end +``` + +Here: + +- **Object**: `(problem, initial_guess)` +- **Strategies**: `modeler` and `solver` +- **Result**: `solution` + +The function is a *pipeline*: it sequences three atomic actions, each mediated by one strategy. + +### 2.2 The atomic action + +An atomic action is simply calling a strategy directly on an object: + +```julia +function build_model(prob, initial_guess, modeler) + return modeler(prob, initial_guess) +end +``` + +`build_model` is a thin dispatch wrapper. The real work is `modeler(prob, initial_guess)` โ€” the +strategy *is* the callable. It holds its options and applies them when invoked. The same pattern +holds for the solver: `solver(nlp; display)` is the atomic call. + +### 2.3 Why this pattern matters + +- **Extensibility**: a new strategy (modeler, solver, โ€ฆ) is added by defining a new type and its + callable. No existing pipeline code changes. +- **Composability**: a pipeline is a sequence of atomic actions; pipelines can be nested or + composed without touching the strategies themselves. +- **Option routing**: because each strategy declares its options in metadata, a flat bag of + `kwargs` at the pipeline level can be routed to the correct strategy automatically (see ยง5.5). +- **Separation of concerns**: the object carries the problem data; the strategies carry the + method configuration; the pipeline carries the control flow. + +## 3. Three layers of abstraction + +```text +AbstractStrategy (root contract) + โ”‚ + โ”œโ”€โ”€ AbstractStrategyFamily (business grouping: AbstractNLPSolver, + โ”‚ AbstractNLPModeler, โ€ฆ) + โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€ ConcreteStrategy (Ipopt, ADNLP, Collocation, โ€ฆ) + โ”‚ + โ””โ”€โ”€ AbstractStrategyParameter (singleton for type-level specialization: + CPU, GPU, โ€ฆ) +``` + +- **Root** โ€” `AbstractStrategy` defines the *generic* contract (identity, metadata, options). +- **Family** โ€” an abstract subtype that groups interchangeable strategies and adds a **business contract** (e.g. a solver must be callable on an NLP, a modeler must build a model and a solution). +- **Concrete strategy** โ€” a struct subtyping a family, holding only an `options` field. +- **Parameter** โ€” a singleton type used for **type-level** specialization (compile-time dispatch, distinct defaults per backend, e.g. `CPU` vs `GPU`). Parameters are not runtime values. + +## 4. The contract + +The contract is intentionally split into a **type level** (no instance needed) and an **instance level** (configured object). This separation enables registry lookup, option routing and validation **before** any resource allocation. + +### 4.1 Root contract โ€” `AbstractStrategy` + +**Type-level methods** (callable on the type itself): + +- `Strategies.id(::Type{<:S}) -> Symbol` โ€” unique identifier of the strategy. +- `Strategies.metadata(::Type{<:S}) -> StrategyMetadata` โ€” collection of `OptionDefinition`s, each carrying: + - `name::Symbol`, + - `type::Type`, + - `default` (any), + - `description::String`, + - optional `aliases`, + - optional `validator` (called during construction). + +**Instance-level method**: + +- `Strategies.options(s::S) -> StrategyOptions` โ€” access to the validated options of an instance, with provenance tracking. + +**Canonical constructor**: + +```julia +function S(; mode::Symbol = :strict, kwargs...) + opts = Strategies.build_strategy_options(S; mode = mode, kwargs...) + return S(opts) +end +``` + +`build_strategy_options` performs name resolution (including aliases), type checking, validator execution, provenance marking, and Levenshtein-based suggestions on typos. The `mode` argument selects between: + +- `:strict` โ€” unknown option names are rejected; +- `:permissive` โ€” unknown options are accepted with `:user` provenance and bypass type validation (useful for backend-specific kwargs). + +### 4.2 Family-level contract + +Each family **adds requirements on top of the root contract**. Two examples taken from CTSolvers: + +- `AbstractNLPModeler` requires two callables: + - `(modeler)(prob, initial_guess) -> NLP` (build the NLP model), + - `(modeler)(prob, nlp_stats) -> Solution` (build the user-facing solution). +- `AbstractNLPSolver` requires one callable: + - `(solver)(nlp; display) -> AbstractExecutionStats`. + +Solvers additionally use a **Tag dispatch** pattern so that the strategy *type* can be defined in the main package while the actual implementation lives in a package extension (loaded only when the backend dependency is available). This lets the registry know about a solver before the heavy dependency is brought in. + +### 4.3 Parameter contract + +A parameter type must: + +1. subtype `Strategies.AbstractStrategyParameter`, +2. be a **singleton** (no fields), +3. implement `Strategies.id(::Type{<:P}) -> Symbol`. + +Parameters can be passed as the first positional argument of a strategy constructor (`S(GPU; kwargs...)`) and are used to specialize `metadata(S, ::Type{P})` โ€” typically to provide different defaults per backend. + +## 5. What you get once the contract is honored + +Honoring the contract is what unlocks the rest of the infrastructure. The strategy author writes a small struct, an `id`, a `metadata`, a constructor and an `options` accessor, and from there: + +### 5.1 Validation + +- `Strategies.validate_strategy_contract(S)` checks that `id`, `metadata`, `options` and the canonical constructor are in place. +- `OptionDefinition` validators are run automatically at construction. +- Typos trigger a friendly error with the closest valid option name. + +### 5.2 Registry and resolution + +- `Strategies.create_registry(Family => (S1, S2, โ€ฆ), โ€ฆ)` builds a mapping family โ†” concrete strategies. +- `strategy_ids(Family, registry)`, `type_from_id(:id, Family, registry)` query it. +- `build_strategy(:id, Family, registry; kwargs...)` constructs an instance from a symbol. +- `build_strategy_from_method(method, Family, registry; kwargs...)` accepts a **method tuple** like `(:collocation, :adnlp, :ipopt)` and extracts the right symbol per family. This is the basis of multi-strategy selection. + +### 5.3 Type-level introspection (no allocation) + +Available without instantiating a strategy: + +- `option_names(S)`, +- `option_defaults(S)`, +- `option_type(S, :name)`, +- `option_description(S, :name)`. + +### 5.4 Provenance + +The `StrategyOptions` of an instance distinguishes user-provided values from defaults: + +- `Strategies.is_user(opts, :name)`, +- `Strategies.is_default(opts, :name)`, +- `Strategies.source(opts, :name)`. + +This is essential when several layers (user, orchestrator, defaults) may want to set the same option: a layer can decide to override only what the user did *not* set explicitly. + +### 5.5 Orchestration of options + +Because every option is declared in metadata, a single bag of `kwargs...` provided by the user can be **routed** to the right family automatically. This is what enables a high-level call such as + +```julia +solve(problem, x0, modeler, solver; max_iter = 1000, grid_size = 500, parameter = :gpu) +``` + +to dispatch each option to the correct strategy without the user having to know which family owns which option. + +### 5.6 Functional vs object level (CommonSolve) + +Once a family's callable contract is implemented, the [`CommonSolve`](https://github.com/SciML/CommonSolve.jl) integration provides three coherent levels of API "for free": + +- **High level** โ€” full pipeline: + `solve(problem, x0, modeler, solver) -> Solution`. +- **Mid level** โ€” NLP โ†’ stats: + `solve(nlp, solver) -> AbstractExecutionStats`. +- **Low level** โ€” flexible dispatch: + `solve(any_compatible_object, solver)` falls back to `solver(any_compatible_object)`. + +The high-level form is a *recipe* that internally calls the atomic operations (`build_model`, `solver(nlp)`, `build_solution`). Both levels coexist cleanly precisely because each step is mediated by a strategy that respects the contract. + +### 5.7 Parameter-based specialization + +When a strategy declares parameters in its registry entry, the orchestration can resolve a user-provided `parameter = :gpu` keyword into the corresponding singleton type and invoke the parameterized constructor. The strategy can then expose **different defaults** per parameter via `metadata(S, ::Type{P})`, all decided at compile time. + +## 6. Higher-level orchestration: descriptive, explicit, canonical + +The CommonSolve three-level API of ยง5.6 is the *atomic* layer. On top of it, one can build a **user-facing orchestration layer** that exploits the strategy contract (identifiers, metadata, registry, parameters) to offer much more flexible call shapes. [`OptimalControl.jl/src/solve/`](https://github.com/control-toolbox/OptimalControl.jl/tree/main/src/solve) is a reference implementation of this idea, organized in three tiers. + +### 6.1 Descriptive solve โ€” symbolic description + flat options + +The user provides a partial or complete *symbolic description* of the method, plus a single flat bag of options: + +```julia +solve(ocp, :collocation, :adnlp, :ipopt; grid_size = 100, max_iter = 500, display = false) +``` + +The orchestration: + +1. **completes** the description if partial, by walking a priority list of valid method tuples; +2. **routes** each kwarg to the correct family using metadata (an option declared by `:ipopt` goes to the solver, `:grid_size` to the discretizer, โ€ฆ); +3. **builds** the concrete strategy instances via the registry; +4. **delegates** to the canonical solve. + +When the same option name is declared by several families, disambiguation is explicit: + +```julia +solve(ocp, :collocation, :adnlp, :ipopt; + backend = route_to(adnlp = :sparse, ipopt = :cpu)) +``` + +### 6.2 Explicit solve โ€” typed components, partial completion + +The user passes already-built strategy instances as keyword arguments, identified by their **abstract family supertype**. Missing components are completed via the registry, using the priority list: + +```julia +solve(ocp; discretizer = Collocation(grid_size = 100), solver = Ipopt()) +# modeler completed from the registry's first available choice +``` + +This is convenient when the user wants to build a strategy with non-trivial options up-front and let the system fill in the rest. + +### 6.3 Canonical solve โ€” pure execution + +The lowest-level call: every component is concrete and fully specified, no defaults, no normalization, no completion. This is the layer that all higher tiers eventually delegate to. + +```julia +solve(ocp, init, discretizer, modeler, solver; display) +``` + +### 6.4 Supporting infrastructure + +Two pieces make the higher tiers possible: + +- **A method priority list** โ€” an ordered tuple of valid quadruplets `(discretizer_id, modeler_id, solver_id, parameter)`. It plays the role of both a *whitelist* of supported combinations and a *priority order* used to complete a partial description. See `OptimalControl.jl/src/helpers/methods.jl`. +- **A strategy registry** โ€” the single source of truth `Family => (Strategy, [Parameters...])`, declaring which concrete strategies belong to which family and which parameters (e.g. `CPU`, `GPU`) each one supports. See `OptimalControl.jl/src/helpers/registry.jl`. +- **Strategy builders** โ€” recursive helpers that turn a method tuple plus routed options into concrete, parameterized strategy instances. See `OptimalControl.jl/src/helpers/strategy_builders.jl`. + +### 6.5 Why three tiers? + +The descriptive tier is the user-facing *recipe*: short, declarative, almost natural language. The canonical tier is the atomic execution layer, used by orchestration code and by power users who already hold concrete strategies. The explicit tier is the bridge: typed inputs, partial inputs allowed, completion deterministic. All three rest on the same strategy contract โ€” identifiers for symbolic dispatch, metadata for option routing, registry for completion. + +### 6.6 Introspection on top of the registry: `describe` + +A registry-aware `describe(:id)` can be exposed as a thin convenience wrapper that calls the generic `CTSolvers.describe(:id, registry)` with the package's own registry pre-bound. It prints the strategy's options, types, defaults and descriptions โ€” all of which are already available through the strategy contract. See [`OptimalControl.jl/src/helpers/describe.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/helpers/describe.jl) for an example. This is essentially free once the registry is in place. + +### 6.7 Where does the registry live? + +The registry is the **single source of truth** for which strategies and parameters are available. It must therefore live in the package that has visibility on **all** strategies it wants to compose. In the Control Toolbox stack: + +- **CTFlows** will be a dependency of **OptimalControl**, not the host of the orchestration layer. +- It is **OptimalControl** that owns and maintains the global registry, the `methods()` priority list, and the `solve` / `describe` user-facing functions. +- CTFlows' job is to **define its own families and concrete strategies** and to honor the strategy contract, so that OptimalControl can register them alongside those of CTSolvers, CTDirect, etc. + +In other words: CTFlows produces strategies; OptimalControl orchestrates them. + +## 7. Why this matters for CTFlows + +The CTFlows refactoring described in [`roadmap.md`](roadmap.md) repeatedly hits dimensions of choice that map naturally onto this pattern: + +- pluggable ODE integrator backends with a default and a `using`-based opt-in, +- open-loop / closed-loop / dynamic-closed-loop encapsulations, +- functional vs object-level API for flow construction, integration, modeling, +- CPU vs GPU execution, +- modular differential-geometry operators. + +Each such dimension can become a **family** with its own business contract, populated by **concrete strategies**, and possibly specialized by **parameters**. Identifying these families, drafting their contracts and listing the concrete strategies to implement is the next step. + +## 8. Next step + +A follow-up report will: + +- enumerate the candidate strategy **families** for CTFlows, +- propose a **business contract** (required methods / signatures) per family, +- list the first **concrete strategies** to implement, +- and connect each family back to the corresponding roadmap items. + +## References + +Guides: + +- [Implementing a Strategy](https://github.com/control-toolbox/CTSolvers.jl/blob/main/docs/src/guides/implementing_a_strategy.md) +- [Strategy Parameters](https://github.com/control-toolbox/CTSolvers.jl/blob/main/docs/src/guides/strategy_parameters.md) +- [Implementing a Modeler](https://github.com/control-toolbox/CTSolvers.jl/blob/main/docs/src/guides/implementing_a_modeler.md) +- [Implementing a Solver](https://github.com/control-toolbox/CTSolvers.jl/blob/main/docs/src/guides/implementing_a_solver.md) + +Source code: + +- [`src/Strategies/Strategies.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/Strategies.jl) +- [`src/Strategies/contract/abstract_strategy.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/abstract_strategy.jl) +- [`src/Strategies/contract/metadata.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/metadata.jl) +- [`src/Strategies/contract/parameters.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/parameters.jl) +- [`src/Strategies/contract/strategy_options.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/strategy_options.jl) +- [`src/Strategies/api/`](https://github.com/control-toolbox/CTSolvers.jl/tree/main/src/Strategies/api) +- [`src/Solvers/abstract_solver.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Solvers/abstract_solver.jl) +- [`src/Solvers/common_solve_api.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Solvers/common_solve_api.jl) +- [`src/Solvers/ipopt.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Solvers/ipopt.jl) +- [`ext/CTSolversIpopt.jl`](https://github.com/control-toolbox/CTSolvers.jl/blob/main/ext/CTSolversIpopt.jl) + +Orchestration example (OptimalControl.jl): + +- [`src/solve/descriptive.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/solve/descriptive.jl) +- [`src/solve/explicit.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/solve/explicit.jl) +- [`src/solve/canonical.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/solve/canonical.jl) +- [`src/helpers/methods.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/helpers/methods.jl) +- [`src/helpers/registry.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/helpers/registry.jl) +- [`src/helpers/strategy_builders.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/helpers/strategy_builders.jl) +- [`src/helpers/describe.jl`](https://github.com/control-toolbox/OptimalControl.jl/blob/main/src/helpers/describe.jl) diff --git a/reports/save/v1/VectorField.md b/reports/save/v1/VectorField.md new file mode 100644 index 00000000..3f267a9b --- /dev/null +++ b/reports/save/v1/VectorField.md @@ -0,0 +1,269 @@ +# Flow from a vector field + +## Goal + +Define a flow from a vector field by going through the full CTFlows pipeline +(`build_system` โ†’ `build_flow` โ†’ `f(config)` โ†’ `solve`). A vector field is a function +returning the derivative of the state, declined along two trait axes โ€” autonomous vs. +non-autonomous (`f(x)` vs. `f(t, x)`) and fixed vs. non-fixed (with an extra variable +`v`: `f(t, x, v)`) โ€” and along the shape of the state (scalar, vector; matrix deferred). + +Two call modes are supported via the config types introduced in +[`design.md`](design.md): + +- `flow(StatePointConfig(t0, x0[, p0], tf))` โ€” returns the final state `xf` (and costate + `pf` if `p0` was provided). For vector fields, no costate; `StatePointConfig(t0, x0, tf)`. +- `flow(StateTrajectoryConfig((t0, tf), x0))` โ€” returns a solution object containing the + full trajectory. + +The high-level convenience: + +```julia +solve(system, config, integrator) # = build_flow + f(config) +``` + +This is delivered in phases. Phase 1 establishes the end-to-end skeleton with the +smallest viable surface; later phases progressively cover the rest. + +--- + +## Phase 1 โ€” In scope + +The first implementation delivers an end-to-end vector-field flow with: + +### Types and constructors + +- **`VectorField` type** โ€” trait matrix `{Autonomous, NonAutonomous} ร— {Fixed, NonFixed}`, + scalar or vector state. Ported and trimmed from `save/src/types.jl`. + + ```julia + # Autonomous, Fixed + vf = VectorField(x -> -x) + + # NonAutonomous, Fixed + vf = VectorField((t, x) -> -x) + + # NonAutonomous, NonFixed (with variable v) + vf = VectorField((t, x, v) -> -x .* v) + ``` + +- **`StatePointConfig` and `StateTrajectoryConfig`** โ€” plain config structs (defined in `Core` or + `Systems`) that carry call-mode data and drive dispatch in `build_solution`. For vector + fields only the no-costate constructors are needed in Phase 1: + + ```julia + StatePointConfig(t0, x0, tf) # endpoint call + StateTrajectoryConfig((t0, tf), x0) # trajectory call + ``` + +- **`VectorFieldSystem <: AbstractSystem`** โ€” concrete system wrapping a `VectorField`. + Implements the full `AbstractSystem` contract: + + - `rhs!(system)` โ€” returns the ODE right-hand side closure `(du, u, p, t) -> nothing`, + adapted to the `VectorField` trait (autonomous or not, fixed or not). + - `dimensions(system)` โ€” returns `(n_x = n,)` where `n` is the state dimension. + - `build_solution(raw, flow, ::StatePointConfig)` โ€” extracts and returns the final state `xf` + from the raw `ODESolution` (i.e. `raw.u[end]`, or `raw[end]` for scalar state). + - `build_solution(raw, flow, ::StateTrajectoryConfig)` โ€” returns the raw `ODESolution` directly + (Phase 1; a CTFlows wrapper is deferred). + - `ode_problem(system, config)` โ€” optional contract method used by `SciMLIntegrator` to + assemble a standard `ODEProblem` from the system and the config's initial condition and + time span. + +### Pipeline functions (Phase 1) + +- **`build_system(vf::VectorField)`** โ€” direct dispatch; no strategy, no `ad_backend` + required. Returns a `VectorFieldSystem`. + +- **`build_flow(system, integrator)`** โ€” wraps `(system, integrator)` into a `Flow`. + +- **`(flow::Flow)(config)`** โ€” calls `integrate` then `build_solution`. + +- **`solve(system, config, integrator)`** โ€” `build_flow` + `f(config)`. + +### Integrator + +- **`SciMLIntegrator <: AbstractIntegrator`** โ€” defined in the package extension + `CTFlowsSciML`, loaded when `SciMLBase` and `OrdinaryDiffEqCore` are available. The + user loads any algorithm package (`OrdinaryDiffEqTsit5`, `OrdinaryDiffEq`, + `DifferentialEquations`) and passes `alg = Tsit5()`. + + Full CTSolvers strategy contract (`id`, `metadata`, `options`, `Base.show`, `describe`). + + Phase-1 options (sanity-checked against SciML APIs at implementation time): + + | Name | Type | Default | Description | + | --- | --- | --- | --- | + | `alg` | Any | `Tsit5()` | ODE algorithm | + | `abstol` | Float64 | `1e-10` | Absolute tolerance | + | `reltol` | Float64 | `1e-10` | Relative tolerance | + | `maxiters` | Int | `10^5` | Maximum number of steps | + | `dt` | Float64 | โ€” | Fixed step size (non-adaptive) | + | `adaptive` | Bool | `true` | Adaptive step-size control | + | `save_everystep` | Bool | `true` | Save at every solver step | + | `saveat` | Vector | `[]` | Save at specific times | + + Business callable: builds an `ODEProblem` from the system's `ode_problem` method, calls + `CommonSolve.solve(prob, alg; opts...)`, and returns the `ODESolution`. + +### Tests + +Following `.windsurf/rules/testing.md`: + +- **Unit tests** for `VectorField` (all four trait combinations, scalar and vector state). +- **Unit tests** for `VectorFieldSystem` (`rhs!`, `dimensions`, `build_solution` for both + config types). +- **Unit tests** for `StatePointConfig` and `StateTrajectoryConfig` (construction, field access). +- **Contract tests** for `SciMLIntegrator` (`id`, `metadata`, `options`, `NotImplemented` + path for unimplemented callables). +- **End-to-end pipeline test** (guarded by the SciML extension, using + `OrdinaryDiffEqTsit5` in test extras): integrate `f(t, x) = -x` from `t0 = 0` to + `tf = 1` with `x0 = [1.0]`; check `xf โ‰ˆ [exp(-1)]` and `sol(0.5) โ‰ˆ [exp(-0.5)]`. + +### End-to-end user example (Phase 1) + +```julia +using CTFlows +using OrdinaryDiffEqTsit5 + +# Build system directly from vector field โ€” no strategy, no AD +vf = VectorField((t, x) -> -x) # NonAutonomous, Fixed +sys = build_system(vf) # VectorFieldSystem + +# Build integrator (strategy with options) +integ = SciMLIntegrator(abstol = 1e-12, reltol = 1e-12) + +# Build flow (system + integrator) +flow = build_flow(sys, integ) + +# Endpoint call โ€” returns final state only +xf = flow(StatePointConfig(0.0, [1.0], 1.0)) # โ‰ˆ [exp(-1)] + +# Trajectory call โ€” returns raw ODESolution (Phase 1) +sol = flow(StateTrajectoryConfig((0.0, 1.0), [1.0])) # ODESolution +sol(0.5) # โ‰ˆ [exp(-0.5)] + +# High-level convenience +xf2 = solve(sys, StatePointConfig(0.0, [1.0], 1.0), integ) +``` + +--- + +## Deferred to later phases + +The following items are intentionally **not** in Phase 1: + +- **Matrix-valued vector fields** โ€” Phase 1 supports scalar/vector states only. The + third trait axis (or parametric state shape) needs a design pass. +- **Solution wrapper with getters** โ€” Phase 1 returns the raw SciML `ODESolution`. + Later, `build_solution` for `StateTrajectoryConfig` will produce a CTFlows-specific + `AbstractSolution` subtype exposing `state(sol)`, `time_grid(sol)`, `sol(t)`. +- **Plot recipe** โ€” mirroring `CTModelsPlots`; deferred to a sibling extension + (`CTFlowsPlots`) or a `RecipesBase` extension. +- **`internalnorm` option and ForwardDiff.Dual fix** (issue + [#93](https://github.com/control-toolbox/CTFlows.jl/issues/93)) โ€” only the real part + of `ForwardDiff.Dual` numbers should contribute to the adaptive-step norm. Reference + snippet preserved in the Appendix. +- **Event / jump handling** โ€” `tstops`, `callback`, `jumps` options on `SciMLIntegrator`. +- **GPU support** โ€” `CPU`/`GPU` strategy parameters on `SciMLIntegrator`, mirroring the + Exa modeler pattern in CTSolvers (see Appendix). +- **Concatenation** โ€” `MultiPhaseSystem`, `MultiPhaseFlow`, the `โˆ˜` and `*` operators. +- **Per-algorithm integrator strategies** โ€” dedicated `Tsit5Integrator`, + `Rodas4Integrator`, etc.; Phase 1 ships one generic `SciMLIntegrator`. +- **Costate integration** โ€” `StatePointConfig(t0, x0, p0, tf)` and + `StateTrajectoryConfig((t0, tf), x0, p0)` for Hamiltonian and OCP flows; not relevant for + plain vector fields. + +--- + +## Open questions (Phase โ‰ฅ 2) + +- **Matrix-field shape**: third trait axis, parametric `T <: AbstractArray`, or pure + method dispatch on the function signature? +- **Option ownership**: `tstops` / `jumps` / `internalnorm` on the integrator (they + configure the solver) or on the system (they describe the ODE structure)? +- **Integrator granularity**: keep one generic `SciMLIntegrator` long-term, or introduce + one strategy per algorithm (`Tsit5Integrator`, `Rodas4Integrator`, โ€ฆ) sharing a common + metadata base? +- **GPU pathway**: mirror `exa.jl` exactly with `CPU`/`GPU` parameters on + `SciMLIntegrator{P}` and computed defaults, or carry the parameter on the AD backend, + or both? +- **Solution wrapper API**: CTFlows-specific type with `state(sol)` / `time_grid(sol)`, + or just return the SciML `ODESolution` and document `sol(t)` as the canonical accessor? +- **Plot recipe location**: extension on `RecipesBase`/`Plots`, or a sibling package + `CTFlowsPlots` mirroring `CTModelsPlots`? +- **SciML option-name verification**: every option name in the Phase-1 list must be + checked against the actual `SciMLBase` / `OrdinaryDiffEqCore` APIs at implementation + time. + +--- + +## Appendix โ€” reference snippets + +### Dual-number internal norm (issue #93) + +Reference snippet for the future `internalnorm` fix โ€” only the real part of +`ForwardDiff.Dual` numbers should contribute to the adaptive-step norm: + +```julia +sse(x::Number) = x^2 +sse(x::ForwardDiff.Dual) = sse(ForwardDiff.value(x)) + +totallength(x::Number) = 1 +totallength(x::ForwardDiff.Dual) = totallength(ForwardDiff.value(x)) +totallength(x::AbstractArray) = sum(totallength, x) + +my_norm(u, t) = sqrt(sum(sse, u) / totallength(u)) + +# Usage: SciMLIntegrator(internalnorm = my_norm) +``` + +### CPU/GPU parameter template (from CTSolvers `exa.jl`) + +Reference pattern for parameterising a CTFlows strategy by `CPU`/`GPU`: + +```julia +# Parameter types (singletons, subtype AbstractStrategyParameter) +struct CPU <: Strategies.AbstractStrategyParameter end +struct GPU <: Strategies.AbstractStrategyParameter end + +# Parameterised strategy type +struct SciMLIntegrator{P <: Union{CPU, GPU}} <: AbstractIntegrator + options::Strategies.StrategyOptions +end + +# Default constructor: CPU +SciMLIntegrator(; kwargs...) = SciMLIntegrator{CPU}(; kwargs...) +SciMLIntegrator(::Type{P}; kwargs...) where {P} = SciMLIntegrator{P}( + Strategies.build_strategy_options(SciMLIntegrator{P}; kwargs...) +) + +# Parameter-specific metadata (different defaults per backend) +function Strategies.metadata(::Type{<:SciMLIntegrator{CPU}}) + return Strategies.StrategyMetadata( + Strategies.OptionDefinition(name=:alg, type=Any, default=Tsit5(), + description="ODE algorithm (CPU default: Tsit5)"), + # โ€ฆ other options โ€ฆ + ) +end + +function Strategies.metadata(::Type{<:SciMLIntegrator{GPU}}) + return Strategies.StrategyMetadata( + Strategies.OptionDefinition(name=:alg, type=Any, default=GPUTsit5(), + description="ODE algorithm (GPU default: GPUTsit5)"), + # โ€ฆ other options โ€ฆ + ) +end +``` + +### References + +- `save/src/types.jl` โ€” original `VectorField` implementation (`L38-L54` traits, + `L626-L692` type) +- `save/ext/vector_field.jl` โ€” original `Flow(::VectorField; ...)` extension +- [`Tsit5` documentation](https://docs.sciml.ai/OrdinaryDiffEq/stable/explicit/Tsit5/) +- [`DifferentiationInterface.jl` backends](https://juliadiff.org/DifferentiationInterface.jl/DifferentiationInterface/stable/explanation/backends/) +- [CTSolvers โ€” strategy parameters](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Strategies/contract/parameters.jl) +- [CTSolvers โ€” Exa modeler with CPU/GPU](https://github.com/control-toolbox/CTSolvers.jl/blob/main/src/Modelers/exa.jl) +- [CTFlows issue #93 โ€” dual-number internal norm](https://github.com/control-toolbox/CTFlows.jl/issues/93) +- [`reports/v1/design.md`](design.md) โ€” overall CTFlows v1 architecture diff --git a/reports/save/v1/critique_ctflows.md b/reports/save/v1/critique_ctflows.md new file mode 100644 index 00000000..e2b87449 --- /dev/null +++ b/reports/save/v1/critique_ctflows.md @@ -0,0 +1,445 @@ +# Rapport critique โ€” CTFlows.jl (branche `develop`) + +--- + +## Points 1 & 2 โ€” Indirection des callables et couplage de la couche Solutions *(points liรฉs)* + +Ces deux points forment un seul problรจme de conception et doivent รชtre traitรฉs ensemble. + +### Problรจme 1 โ€” Callables redondants dans l'intรฉgrateur + +`calling.jl` dรฉfinit bien trois fonctions nommรฉes distinctes, mais chacune n'est qu'un relais vers un callable surchargรฉ sur l'intรฉgrateur : + +```julia +# calling.jl โ€” trois wrappers qui dรฉlรจguent aux callables +build_problem(sys, config, int; variable) = int(sys, config; variable=variable) +solve_problem(prob, int) = int(prob) +build_solution(ode_sol, sys, config, int) = int(ode_sol, sys, config) +``` + +```julia +# CTFlowsSciML.jl โ€” trois callables sur le mรชme struct SciML +(int::SciML)(sys, config; variable) = ODEProblem(...) # construire +(int::SciML)(prob::AbstractODEProblem) = solve(...) # rรฉsoudre +(int::SciML)(ode_sol, sys, config) = build_solution(...) # emballer +``` + +On a deux niveaux pour la mรชme chose. Le callable `(int::SciML)(args...)` est surchargรฉ trois fois avec des comportements radicalement diffรฉrents selon les types d'arguments โ€” inhabituel en Julia et opaque ร  la lecture. + +### Problรจme 2 โ€” La couche Solutions connaรฎt les types SciML + +Dans `Solutions/building.jl`, `build_solution` reรงoit directement une `SciMLBase.AbstractODESolution` et accรจde ร  `ode_sol.u[end]` : + +```julia +function build_solution(ode_sol::SciMLBase.AbstractODESolution, + sys::Systems.VectorFieldSystem, config::StatePointConfig) + final = ode_sol.u[end] # connaissance directe de la structure SciML + return config.x0 isa Number ? final[1] : final +end +``` + +Supprimer l'intรฉgrateur de `build_solution` ne suffit pas ร  rรฉgler ce couplage : si `calling.jl` appelle `Solutions.build_solution(ode_sol, sys, config)` directement, Solutions doit quand mรชme connaรฎtre la structure interne de SciML. **L'intรฉgrateur n'a pas ร  connaรฎtre Solutions, mais Solutions ne doit pas non plus connaรฎtre SciML.** + +### Proposition unifiรฉe : `AbstractIntegrationResult` avec accesseurs sรฉmantiques + +**ร‰tape 1 โ€” Dรฉfinir `AbstractIntegrationResult` et ses accesseurs dans `Common/` ou `Solutions/` :** + +```julia +abstract type AbstractIntegrationResult end + +# Contrat minimal : trois accesseurs sรฉmantiques +final_state(r::AbstractIntegrationResult) = throw(NotImplemented(...)) +times(r::AbstractIntegrationResult) = throw(NotImplemented(...)) +evaluate_at(r::AbstractIntegrationResult, t) = throw(NotImplemented(...)) +``` + +**ร‰tape 2 โ€” Implรฉmenter le type concret dans l'extension SciML :** + +```julia +# CTFlowsSciML.jl โ€” le seul endroit qui connaรฎt ode_sol.u, ode_sol.t, etc. +struct SciMLIntegrationResult{S<:SciMLBase.AbstractODESolution} <: AbstractIntegrationResult + ode_sol::S +end + +final_state(r::SciMLIntegrationResult) = r.ode_sol.u[end] +times(r::SciMLIntegrationResult) = r.ode_sol.t +evaluate_at(r::SciMLIntegrationResult, t) = r.ode_sol(t) +``` + +**ร‰tape 3 โ€” `solve_problem` retourne un `AbstractIntegrationResult`, pas une solution brute :** + +```julia +function Flows.solve_problem(int::SciML, prob) + ode_sol = SciMLBase.solve(prob; Strategies.options_dict(int)...) + return SciMLIntegrationResult(ode_sol) +end +``` + +**ร‰tape 4 โ€” `Solutions/building.jl` n'utilise que les accesseurs, sans dรฉpendance SciML :** + +```julia +function build_solution(result::AbstractIntegrationResult, + sys::VectorFieldSystem, config::StatePointConfig) + return final_state(result) +end + +function build_solution(result::AbstractIntegrationResult, + sys::VectorFieldSystem, config::StateTrajectoryConfig) + return VectorFieldSolution(result) +end +``` + +**ร‰tape 5 โ€” `VectorFieldSolution` enveloppe `AbstractIntegrationResult`, pas une solution SciML :** + +```julia +struct VectorFieldSolution{R<:AbstractIntegrationResult} <: AbstractVectorFieldSolution + result::R +end + +(sol::VectorFieldSolution)(t::Real) = evaluate_at(sol.result, t) +``` + +**ร‰tape 6 โ€” `calling.jl` appelle `Solutions.build_solution` directement, sans passer l'intรฉgrateur. Le contrat de `AbstractIntegrator` se rรฉduit ร  deux mรฉthodes nommรฉes :** + +```julia +# abstract_integrator.jl +function build_problem(int::AbstractIntegrator, sys, config; variable) + throw(NotImplemented(...)) +end +function solve_problem(int::AbstractIntegrator, prob) + throw(NotImplemented(...)) +end + +# calling.jl +function call(flow, config; variable=nothing) + sys = system(flow); int = integrator(flow) + prob = build_problem(int, sys, config; variable) + result = solve_problem(int, prob) # โ†’ AbstractIntegrationResult + return Solutions.build_solution(result, config) # plus d'intรฉgrateur ici +end +``` + +Les callables `(int::SciML)(...)` disparaissent entiรจrement. + +### Bilan des dรฉpendances aprรจs refactorisation + +``` +calling.jl โ†’ Systems, Integrators, Solutions (pas SciML) +Integrators/SciML โ†’ Systems, SciML, Common (produit AbstractIntegrationResult) +Solutions/ โ†’ AbstractIntegrationResult via accesseurs (pas SciML) +CTFlowsSciML โ†’ SciML (seul endroit oรน .u, .t, l'interpolation sont accรฉdรฉs) +``` + +--- + +## Point 3 โ€” `build_integrator` avec dispatch par `Symbol` inutilement complexe + +### Contexte + +La hiรฉrarchie `AbstractIntegrator <: CTSolvers.Strategies.AbstractStrategy` est un bon choix et n'est **pas** de la sur-ingรฉnierie : elle offre gratuitement la gestion des options typรฉes et validรฉes, la description de la mรฉthode, et le systรจme d'alias. Ce n'est pas lร  que le problรจme se situe. + +### Problรจme ciblรฉ + +Le dispatch par `Symbol` dans `build_integrator` n'a de valeur que s'il y a plusieurs intรฉgrateurs ร  distinguer. Aujourd'hui il ne fait que valider que l'utilisateur a bien รฉcrit `:sciml`, ce qui est une friction sans bรฉnรฉfice. L'`id` dans la signature de `Flow` est un dรฉtail d'implรฉmentation exposรฉ inutilement ร  l'utilisateur : + +```julia +function build_integrator(id::Symbol; kwargs...) + if id === :sciml + return SciML(; kwargs...) + else + throw(IncorrectArgument(...)) + end +end + +function Flow(data::Data.VectorField, id::Symbol=:sciml; opts...) + ... +end +``` + +### Proposition + +Supprimer l'`id` et simplifier les deux fonctions : + +```julia +# Integrators/building.jl +function build_integrator(; kwargs...) + return SciML(; kwargs...) +end + +# Flows/building.jl +function Flow(data::Data.VectorField; opts...) + system = Systems.build_system(data) + integrator = Integrators.build_integrator(; opts...) + return Flow(system, integrator) +end +``` + +L'API utilisateur devient plus propre : + +```julia +flow = Flow(vf; reltol=1e-10, alg=Rodas4()) # avant : Flow(vf, :sciml; ...) +``` + +Si un second intรฉgrateur arrive un jour, le dispatch par `Symbol` (ou mieux, par type) pourra รชtre rรฉintroduit avec un vrai cas d'usage pour le valider. En l'รฉtat, c'est du code dรฉfensif contre un besoin hypothรฉtique. + +--- + +## Point 4 โ€” Le slot `p` de SciML comme canal de transmission implicite + +### Problรจme + +La variable d'un systรจme `NonFixed` est transmise via le champ `p` de l'`ODEProblem` SciML. Ce contrat n'existe qu'en commentaire dans `vector_field_system.jl`, sans aucun type pour le formaliser. Quiconque lit l'extension SciML sans ce contexte ne comprend pas pourquoi `p` est tantรดt `nothing`, tantรดt une valeur mรฉtier. Si on veut ajouter des paramรจtres numรฉriques rรฉels en plus de la variable mรฉtier, ce contrat implicite se casse silencieusement. + +### Proposition + +Introduire un type wrapper explicite pour ce qui transite dans `p` : + +```julia +# Dans Common/ ou Systems/ +struct ODEParameters{V} + variable::V # nothing pour Fixed, valeur pour NonFixed +end +``` + +La construction du problรจme devient : + +```julia +p = ODEParameters(variable) +ODEProblem(rhs!(sys), u0, tspan, p) +``` + +Et la closure RHS lit explicitement `p.variable` : + +```julia +rhs = (du, u, p, t) -> begin + du .= vf(t, u, p.variable) + nothing +end +``` + +Le contrat est maintenant dans le type. Si on ajoute un jour d'autres paramรจtres (callbacks, donnรฉes supplรฉmentaires...), `ODEParameters` s'รฉtend naturellement sans casser le reste. + +--- + +## Point 5 โ€” `VectorFieldSolution` sans accesseurs sรฉmantiques + +### Problรจme + +`VectorFieldSolution` n'expose que `sol(t)` (dรฉlรฉgation ร  SciML) et `raw(sol)`. L'extension Plots dรฉlรจgue elle-mรชme directement ร  `raw(sol)`. Le wrapper n'apporte donc pas encore de valeur concrรจte ร  l'utilisateur, et le force ร  connaรฎtre la structure interne de SciML pour accรฉder aux donnรฉes de sa trajectoire. + +### Proposition + +Maintenant que `VectorFieldSolution` enveloppe un `AbstractIntegrationResult` (voir Points 1 & 2), les accesseurs s'appuient naturellement sur l'interface de ce type, sans aucune connaissance de SciML. + +**Accรจs ร  la grille temporelle โ€” deux noms, mรชme sรฉmantique :** + +```julia +times(sol::VectorFieldSolution) = times(sol.result) +time_grid(sol::VectorFieldSolution) = times(sol) # alias +``` + +`times` et `time_grid` sont deux noms pour la mรชme chose. `time_grid` est plus explicite dans un contexte numรฉrique, `times` est plus court ร  l'usage courant. + +**Accรจs ร  l'รฉtat comme fonction du temps :** + +```julia +state(sol::VectorFieldSolution) = sol + +(sol::VectorFieldSolution)(t::Real) = evaluate_at(sol.result, t) +``` + +`state(sol)` retourne `sol` elle-mรชme, qui est dรฉjร  callable โ€” pas d'allocation de closure supplรฉmentaire. L'idiome utilisateur devient : + +```julia +x = state(sol) # x est une fonction du temps +x(0.0) # รฉtat initial +x(0.5) # รฉtat interpolรฉ ร  t = 0.5 +x.(0.0:0.1:1.0) # broadcasting sur une grille +``` + +`state` est un **accesseur sรฉmantique** : l'utilisateur travaille avec une fonction trajectoire sans savoir qu'il manipule un `VectorFieldSolution` callable. C'est la fondation naturelle d'une interface cohรฉrente pour le contrรดle optimal โ€” on pourra รฉcrire `x = state(sol)`, `p = costate(sol)`, `u = control(sol)` de faรงon uniforme si la solution est รฉtendue aux systรจmes hamiltoniens. + +**Ce que รงa donne cรดtรฉ `CTFlowsPlots` :** + +L'extension Plots peut dรฉsormais utiliser `times` et `sol` directement via une fonction interne partagรฉe par les trois surcharges de `plot` : + +```julia +function _sol_to_arrays(sol::Solutions.VectorFieldSolution) + ts = Solutions.times(sol) + x = Solutions.state(sol) + states = reduce(hcat, x.(ts))' + return ts, states +end + +function Plots.plot(sol::Solutions.VectorFieldSolution; kwargs...) + ts, states = _sol_to_arrays(sol) + return Plots.plot(ts, states; kwargs...) +end +``` + +`reduce(hcat, sol.(ts))'` est prรฉfรฉrรฉ ร  `stack(...)` car il gรจre uniformรฉment les deux cas : รฉtat scalaire (1D) et รฉtat vectoriel. `stack` attend des tableaux et รฉchoue si `sol(t)` retourne un scalaire. + +--- + +## Point 6 โ€” Test `isa Number` fragile dans `Solutions/building.jl` + +### Problรจme + +```julia +return config.x0 isa Number ? final[1] : final +``` + +Ce test dรฉtecte indirectement qu'une promotion scalaire a eu lieu lors de la construction de l'`ODEProblem`. C'est un couplage implicite entre deux couches รฉloignรฉes : si l'extension SciML cesse de promouvoir les scalaires en vecteurs, ce code retourne silencieusement le mauvais type sans aucune erreur. + +### Proposition + +Paramรฉtrer `AbstractConfig` avec `X0` pour rendre la distinction scalaire/vecteur visible au niveau du type : + +```julia +abstract type AbstractConfig{X0} end + +struct StatePointConfig{T0<:Real, X0, TF<:Real} <: AbstractConfig{X0} + t0::T0 + x0::X0 + tf::TF +end + +struct StateTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0} <: AbstractConfig{X0} + tspan::TS + x0::X0 +end +``` + +`initial_condition` et `build_solution` peuvent alors toutes deux dispatcher ร  la **compilation** sur le paramรจtre de type, sans aucun test `isa` ร  l'exรฉcution : + +```julia +initial_condition(c::AbstractConfig{<:Number}) = [c.x0] +initial_condition(c::AbstractConfig) = c.x0 +``` + + + +```julia +# Cas scalaire โ€” X0 <: Number +function build_solution(result::AbstractIntegrationResult, sys, + config::StatePointConfig{<:Real, <:Number, <:Real}) + return final_state(result)[1] +end + +# Cas gรฉnรฉral โ€” vecteur +function build_solution(result::AbstractIntegrationResult, sys, config::StatePointConfig) + return final_state(result) +end +``` + +La distinction scalaire/vecteur ne concerne que `StatePointConfig` โ€” pour `StateTrajectoryConfig` on retourne toujours un `VectorFieldSolution` quelle que soit la dimension, donc pas de cas particulier ร  gรฉrer. + +Le paramรจtre `X0` sur `AbstractConfig` ne crรฉe pas de propagation gรชnante : รฉcrire `AbstractConfig` sans paramรจtre dans une signature signifie "tout `AbstractConfig` quel que soit `X0`", ce qui est le comportement habituel de Julia. + +--- + +## Point 7 โ€” Aucune vรฉrification du `retcode` de la solution ODE + +### Problรจme + +Aprรจs `solve(prob, ...)`, la solution est emballรฉe sans aucun contrรดle. Si SciML produit un `retcode` de type `Unstable`, `MaxIters` ou `DtLessThanMin`, l'utilisateur reรงoit silencieusement des `NaN` ou des valeurs absurdes. C'est un dรฉfaut critique pour un usage en contrรดle optimal oรน une intรฉgration ratรฉe peut propager des erreurs sans avertissement dans un algorithme de tir. + +### Quelle exception utiliser ? + +Aucune exception existante dans CTBase ne convient : + +- `IncorrectArgument` est rรฉservรฉe aux **donnรฉes d'entrรฉe invalides** โ€” les arguments peuvent รชtre parfaitement valides et l'ODE diverger quand mรชme. +- `PreconditionError` dรฉsigne un **mauvais ordre d'appel** ou un รฉtat interdit โ€” hors sujet ici. +- Les autres (`ParsingError`, `AmbiguousDescription`, `ExtensionError`) sont encore plus รฉloignรฉes. + +**Recommandation : ajouter `SolverFailure` dans CTBase.** Ce n'est pas une exception propre ร  CTFlows โ€” c'est une catรฉgorie d'erreur transverse ร  toute la toolbox : intรฉgration ODE ratรฉe ici, solveur d'optimisation qui ne converge pas dans CTDirect, systรจme linรฉaire mal conditionnรฉ ailleurs. Le champ `retcode` est gรฉnรฉrique : `:Unstable` / `:DtLessThanMin` pour SciML, `:Infeasible` / `:MaxIterations` pour un solveur NLP. + +```julia +# ร€ ajouter dans CTBase/src/Exceptions/types.jl +struct SolverFailure <: CTException + msg::String + retcode::Union{String, Nothing} + suggestion::Union{String, Nothing} + context::Union{String, Nothing} + + function SolverFailure( + msg::String; + retcode::Union{String, Nothing} = nothing, + suggestion::Union{String, Nothing} = nothing, + context::Union{String, Nothing} = nothing, + ) + new(msg, retcode, suggestion, context) + end +end +``` + +### Proposition + +Threader `unsafe` comme kwarg ร  travers la chaรฎne d'appel, exactement comme `variable` est dรฉjร  threadรฉ : + +```julia +# Flow callable +function (f::Flow)(t0, x0, tf; variable=nothing, unsafe=false) + return call(f, StatePointConfig(t0, x0, tf); variable=variable, unsafe=unsafe) +end + +# call +function call(flow, config; variable=nothing, unsafe=false) + prob = Integrators.build_problem(int, sys, config; variable=variable) + result = Integrators.solve_problem(int, prob; unsafe=unsafe) + return Solutions.build_solution(result, config) +end + +# abstract_integrator.jl โ€” stub mis ร  jour +function solve_problem(int::AbstractIntegrator, prob; unsafe=false) + throw(NotImplemented(...)) +end + +# CTFlowsSciML.jl +function Integrators.solve_problem(integ::SciML, prob; unsafe=false) + ode_sol = SciMLBase.solve(prob; Strategies.options_dict(integ)...) + if !unsafe && !SciMLBase.successful_retcode(ode_sol.retcode) + throw(Exceptions.SolverFailure( + "ODE integration failed"; + retcode = string(ode_sol.retcode), + suggestion = "Try tightening tolerances (reltol, abstol) or changing the solver algorithm.", + context = "SciML solve_problem", + )) + end + return SciMLIntegrationResult(ode_sol) +end +``` + +`unsafe` est une dรฉcision qui appartient ร  l'appel, pas ร  la configuration de l'intรฉgrateur. Un utilisateur qui dรฉbogue une trajectoire divergente peut faire `flow(t0, x0, tf; unsafe=true)` ponctuellement, sans avoir ร  recrรฉer son flow. L'infrastructure est dรฉjร  lร  โ€” `variable` montre exactement le chemin ร  suivre. + +--- + +## Point 8 โ€” Naming `rhs!` trompeur vis-ร -vis des conventions Julia + +### Problรจme + +En Julia, `!` signifie que la fonction modifie un de ses arguments. Or `rhs!(system)` ne modifie rien : elle retourne une closure. Un dรฉveloppeur Julia expรฉrimentรฉ s'attend ร  ce que `rhs!(system)` modifie `system` en place. + +### Proposition + +Renommer en supprimant le `!` : + +```julia +rhs(system) # retourne la closure (du, u, p, t) -> nothing +``` + +`ode_rhs` ou `vector_field_rhs` rendraient la sรฉmantique encore plus explicite si le contexte l'exige. + +--- + +## Synthรจse et prioritรฉs + +| # | Problรจme | Sรฉvรฉritรฉ | Effort | +|---|----------|----------|--------| +| 7 | Absence de vรฉrification du `retcode` | **Critique** | Faible | +| 1 & 2 | Callables redondants + couplage Solutions/SciML | ร‰levรฉe | Moyen | +| 4 | Slot `p` SciML comme canal implicite | ร‰levรฉe | Faible | +| 6 | Test `isa Number` fragile | Moyenne | Faible | +| 3 | Dispatch par `Symbol` inutile dans `build_integrator` | Moyenne | Faible | +| 5 | `VectorFieldSolution` sans accesseurs sรฉmantiques | Faible | Faible | +| 8 | Naming `rhs!` trompeur | Faible | Trivial | diff --git a/reports/save/v1/design.md b/reports/save/v1/design.md new file mode 100644 index 00000000..5f70309e --- /dev/null +++ b/reports/save/v1/design.md @@ -0,0 +1,366 @@ +# CTFlows Design v1: Objects, Single Strategy, Pipelines + +This document specifies the revised architecture of CTFlows. It replaces the three v0 documents +(`design.md`, `candidate_strategies.md`, `strategies.md`) with a leaner, unified description +built around one key simplification: **a single strategy family**. + +## 1. Overview + +CTFlows organises its code along three concerns: + +- **Objects** โ€” `AbstractSystem`, `AbstractFlow`, `AbstractSolution`, and the config types + `StatePointConfig` / `StateTrajectoryConfig`. They are *what* is acted upon or returned. +- **Single strategy family** โ€” `AbstractIntegrator <: CTSolvers.Strategies.AbstractStrategy`. + This is the only family. It controls *how* the Cauchy problem is solved. +- **Pipelines** โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve`. + Written on the abstract types; concrete implementations plug in without changing the pipeline. + +### What changed from v0 + +| Concern | v0 | v1 | +| --- | --- | --- | +| System construction | `AbstractFlowModeler` (strategy) | Direct dispatch via `build_system` (no strategy) | +| AD backend | `AbstractADBackend` (strategy) | `DifferentiationInterface.jl` backend object (not a strategy) | +| ODE integration | `AbstractIntegrator` (strategy) | `AbstractIntegrator` (strategy) โ€” unchanged | +| **Total families** | **3** | **1** | + +Removing `AbstractFlowModeler` and `AbstractADBackend` from the strategy level reflects +the fact that system construction is determined entirely by the input type (Julia dispatch +is the right mechanism) and that AD configuration is best expressed as a plain +`DifferentiationInterface.jl` backend value, not as a CTSolvers strategy struct. + +--- + +## 2. Config types + +Config types carry the call-mode data and drive dispatch in `build_solution`. They are plain +structs, not strategies. + +### `StatePointConfig` + +```julia +struct StatePointConfig{T, X, P} + t0::T + x0::X + p0::P # nothing if no costate + tf::T +end +``` + +Constructors: + +- `StatePointConfig(t0, x0, tf)` โ€” no costate (`p0 = nothing`) +- `StatePointConfig(t0, x0, p0, tf)` โ€” with costate + +**Semantics**: integrate from `t0` to `tf` starting at `x0` (and `p0` if present); return +only the **final values** `xf` (and `pf` if present). + +### `StateTrajectoryConfig` + +```julia +struct StateTrajectoryConfig{T, X, P} + tspan::Tuple{T, T} + x0::X + p0::P # nothing if no costate +end +``` + +Constructors: + +- `StateTrajectoryConfig(tspan, x0)` โ€” no costate +- `StateTrajectoryConfig(tspan, x0, p0)` โ€” with costate + +**Semantics**: integrate over the full time span; return a **solution object** containing +the trajectory (and possibly the costate) as functions of time. + +--- + +## 3. Objects (non-strategy types) + +### 3.1 `AbstractSystem` + +The fully assembled object that can be integrated. It embeds its own `rhs!`, dimensional +metadata, and solution-building logic โ€” assembled once at `build_system` time. + +```julia +abstract type AbstractSystem end +``` + +**Required methods** (`NotImplemented` defaults): + +```julia +rhs!(system::AbstractSystem) + # Returns a closure (du, u, p, t) -> nothing filling du in place. + +dimensions(system::AbstractSystem) + # Returns a NamedTuple, e.g. (n_x=n,) for a vector field + # or (n_x=n, n_p=n) for a Hamiltonian system. + +build_solution(raw, flow::AbstractFlow, config::AbstractConfig) + # Packages the raw integration result into the appropriate output + # (final values or a solution object), depending on config type. +``` + +**Contract semantics**: + +- `rhs!` returns a closure capturing whatever the system needs. Once obtained, the system + has no further dependency on whatever produced it. +- `dimensions` is the canonical introspection point; used by the integrator to allocate + and by the pipeline to verify compatibility. +- `build_solution` dispatches on `config` type: for `StatePointConfig` it extracts the final + state (and costate); for `StateTrajectoryConfig` it wraps the full trajectory. + +### 3.2 `AbstractFlow` + +A callable object that pairs an `AbstractSystem` with an `AbstractIntegrator`. It +carries no business logic of its own; its job is to expose the integration protocol. + +```julia +abstract type AbstractFlow end +``` + +**Required methods** (`NotImplemented` defaults): + +```julia +(flow::AbstractFlow)(config) # dispatch on StatePointConfig or StateTrajectoryConfig +system(flow::AbstractFlow) # returns the embedded AbstractSystem +integrator(flow::AbstractFlow) # returns the embedded AbstractIntegrator +``` + +**Concrete `Flow{S,I} <: AbstractFlow`** (provided by CTFlows): + +```julia +struct Flow{S <: AbstractSystem, I <: AbstractIntegrator} <: AbstractFlow + system::S + integrator::I +end + +system(f::Flow) = f.system +integrator(f::Flow) = f.integrator + +function (f::Flow)(config) + r = integrate(f.system, config, f.integrator) + return build_solution(r, f, config) +end +``` + +`build_flow` is the canonical constructor: + +```julia +build_flow(system::AbstractSystem, integrator::AbstractIntegrator) = Flow(system, integrator) +``` + +### 3.3 `AbstractSolution` + +Solution wrapper returned by a `StateTrajectoryConfig` call. Distinct from `CTModels.Solution` +(which is an OCP solution); `AbstractSolution` is CTFlows' own trajectory wrapper. + +```julia +abstract type AbstractSolution end +``` + +**Required methods** (`NotImplemented` defaults): + +```julia +state(sol::AbstractSolution) # trajectory x(t) as a callable or array +time_grid(sol::AbstractSolution) # time points +``` + +> **Note**: Phase 1 returns the raw `ODESolution` from SciML rather than a custom +> `AbstractSolution` subtype. The wrapper and its getters are deferred to a later phase. + +--- + +## 4. Single strategy family: `AbstractIntegrator` + +`AbstractIntegrator` is the **only** strategy family in CTFlows. It is +`<: CTSolvers.Strategies.AbstractStrategy` and therefore inherits the full CTSolvers +contract for free: `id`, `metadata`, `options`, `Base.show`, `describe`. + +```julia +abstract type AbstractIntegrator <: CTSolvers.Strategies.AbstractStrategy end +``` + +**Business callable** (`NotImplemented` default): + +```julia +function (integrator::AbstractIntegrator)(ode_problem) + throw(NotImplemented( + "AbstractIntegrator callable not implemented"; + required_method = "(integrator::$(typeof(integrator)))(ode_problem)", + suggestion = "Implement (i::YourIntegrator)(prob) returning an ODE solution.", + context = "AbstractIntegrator call - required method implementation", + )) +end +``` + +The callable receives a fully assembled `ODEProblem` (or equivalent SciML object) and +returns a raw integration result (e.g. an `ODESolution`). It does not know about config +types or solution wrappers โ€” that is `build_solution`'s job. + +**Concrete strategies**: `SciMLIntegrator` (Phase 1, lives in `CTFlowsSciML`) wraps +any SciML algorithm. Later phases may introduce per-algorithm strategies +(`Tsit5Integrator`, `Rodas4Integrator`, โ€ฆ) or GPU-parameterised variants. + +**Candidate options** (Phase 1 subset): + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| `alg` | Any | `Tsit5()` | ODE algorithm | +| `abstol` | Float64 | `1e-10` | Absolute tolerance | +| `reltol` | Float64 | `1e-10` | Relative tolerance | +| `maxiters` | Int | `10^5` | Maximum number of steps | +| `dt` | Float64 | โ€” | Fixed step size (if non-adaptive) | +| `adaptive` | Bool | `true` | Adaptive step size control | +| `save_everystep` | Bool | `true` | Save at every solver step | +| `saveat` | Vector | `[]` | Save at specific times | + +--- + +## 5. Automatic differentiation: not a strategy + +AD in CTFlows is handled via [`DifferentiationInterface.jl`](https://juliadiff.org/DifferentiationInterface.jl/DifferentiationInterface/stable/). + +**Key decisions**: + +- `DifferentiationInterface` and `ForwardDiff` are **direct dependencies** of CTFlows. +- `AutoForwardDiff()` is the **default backend** โ€” no `using ForwardDiff` required from the user. +- Users who want a different backend (e.g. `AutoZygote()`, `AutoEnzyme()`) pass it explicitly + as the `ad_backend` argument to `build_system`. +- The `ad_backend` is a plain `DifferentiationInterface.jl` backend object โ€” **not** a + CTSolvers strategy. There is no `AbstractADBackend` family. + +**Where it is used**: + +- `VectorField` โ†’ no AD needed; `build_system(vf)` takes no `ad_backend`. +- `Hamiltonian` โ†’ AD needed to compute `โˆ‚H/โˆ‚x` and `โˆ‚H/โˆ‚p`: + + ```julia + build_system(H) # uses AutoForwardDiff() by default + build_system(H, ad_backend) # uses the given DI backend + ``` + +- OCP with control law โ†’ same pattern as Hamiltonian. + +**Internal usage**: CTFlows calls `DifferentiationInterface.gradient`, `.jacobian`, etc. +directly, passing the backend provided (or the default). The user never needs to call +these functions directly. + +--- + +## 6. `build_system` โ€” type dispatch, no strategy + +System construction is handled by Julia's multiple dispatch on the input type. There is +no `AbstractFlowModeler` strategy; `build_system` dispatches on the concrete type of its +first argument. + +```julia +# VectorField โ€” no AD +build_system(vf::VectorField) โ†’ VectorFieldSystem + +# Hamiltonian โ€” AD with default backend +build_system(H::Hamiltonian) โ†’ HamiltonianSystem + +# Hamiltonian โ€” AD with explicit backend +build_system(H::Hamiltonian, ad_backend) โ†’ HamiltonianSystem + +# OCP with control law โ€” AD with default backend +build_system(ocp, u) โ†’ OCPSystem + +# OCP with control law โ€” AD with explicit backend +build_system(ocp, u, ad_backend) โ†’ OCPSystem +``` + +Each method constructs the appropriate `AbstractSystem` subtype, embedding the `rhs!`, +dimensional metadata, and the `build_solution` logic at construction time. After +`build_system`, the system is self-contained and the input object is no longer needed. + +--- + +## 7. Pipelines + +All pipeline functions are written on the abstract types declared in ยง3 and ยง4. Concrete +types plug in without modifying the pipeline. + +### 7.1 `build_flow` + +```julia +function build_flow(system::AbstractSystem, integrator::AbstractIntegrator) + return Flow(system, integrator) +end +``` + +### 7.2 `integrate` + +```julia +function integrate(system::AbstractSystem, config, integrator::AbstractIntegrator) + prob = ode_problem(system, config) # build ODEProblem from system + config + return integrator(prob) # integrator's business callable +end +``` + +`ode_problem` is an optional contract method on `AbstractSystem` used by SciML-based +integrators to extract a standard `ODEProblem`. + +### 7.3 Flow callable and `build_solution` + +```julia +function (flow::Flow)(config) + r = integrate(system(flow), config, integrator(flow)) + return build_solution(r, flow, config) +end +``` + +`build_solution` dispatches on the config type: + +```julia +# StatePointConfig โ€” extract and return final values +function build_solution(raw, flow::AbstractFlow, config::StatePointConfig) + # extract xf (and pf if config.p0 โ‰ข nothing) from raw +end + +# StateTrajectoryConfig โ€” wrap full trajectory +function build_solution(raw, flow::AbstractFlow, config::StateTrajectoryConfig) + # return an AbstractSolution wrapping the raw ODESolution +end +``` + +### 7.4 `solve` + +```julia +function solve(system::AbstractSystem, config, integrator::AbstractIntegrator) + f = build_flow(system, integrator) + return f(config) # integrate + build_solution +end +``` + +> **Important**: `solve` operates on a **system** (not a flow). Calling a flow directly +> (`flow(config)`) is also valid and is the idiomatic form when the flow has already been +> built. There is **no `solve` method on `AbstractFlow`**. + +--- + +## 8. Summary + +### Types + +| Type | Kind | Required methods | +| --- | --- | --- | +| `StatePointConfig` | object (config) | constructor | +| `StateTrajectoryConfig` | object (config) | constructor | +| `AbstractSystem` | object | `rhs!`, `dimensions`, `build_solution` | +| `AbstractFlow` | object | `(flow)(config)`, `system`, `integrator` | +| `AbstractSolution` | object | `state`, `time_grid` | +| `Flow{S,I}` | concrete object | provided by CTFlows | +| `AbstractIntegrator` | **strategy** | CTSolvers contract + `(integrator)(prob)` | + +### Pipeline functions + +| Function | Signature | Result | +| --- | --- | --- | +| `build_system` | `(object[, ad_backend])` | `AbstractSystem` | +| `build_flow` | `(system, integrator)` | `Flow` | +| `integrate` | `(system, config, integrator)` | raw result | +| `build_solution` | `(raw, flow, config)` | `xf[,pf]` or `AbstractSolution` | +| `solve` | `(system, config, integrator)` | `xf[,pf]` or `AbstractSolution` | +| `(flow)(config)` | callable | `xf[,pf]` or `AbstractSolution` | diff --git a/reports/save/v1/refactor-integration-result.md b/reports/save/v1/refactor-integration-result.md new file mode 100644 index 00000000..4ab3e6ea --- /dev/null +++ b/reports/save/v1/refactor-integration-result.md @@ -0,0 +1,220 @@ +# Refactor Integration Result Architecture (Points 1 & 2) + +Introduce `AbstractIntegrationResult` with semantic accessors to eliminate redundant callables in `AbstractIntegrator` and fully decouple the `Solutions` layer from SciML types. + +--- + +## What changes and why + +**Point 1 (fully)**: The three callables on `(int::SciML)(...)` and their wrapper counterparts in `calling.jl` are replaced by two named functions โ€” `build_problem` and `solve_problem` โ€” defined as `NotImplemented` stubs on `AbstractIntegrator` in `abstract_integrator.jl` and implemented in `CTFlowsSciML.jl`. No callable form survives. + +**Point 2**: `Solutions` currently imports `SciMLBase` and `build_solution` takes `SciMLBase.AbstractODESolution`. After the refactoring, `Solutions` has no knowledge of SciML: it depends only on `AbstractIntegrationResult` and its semantic accessors (`final_state`, `times`, `evaluate_at`). `SciMLIntegrationResult` lives exclusively in `CTFlowsSciML`. + +**Load-order fix**: `Solutions` currently loads after `Flows` in `CTFlows.jl`. Since `Flows.calling` will call `Solutions.build_solution`, `Solutions` must be moved before `Flows`. + +--- + +## Dependency graph after refactoring + +``` +Flows/calling.jl โ†’ Integrators, Solutions (no SciML) +Integrators/ โ†’ Common, Systems (no Solutions, no SciML) +Solutions/ โ†’ Common, Systems, AbstractIntegrationResult (no SciML, no Integrators) +CTFlowsSciML โ†’ SciML + Solutions + Integrators (sole owner of .u, .t, interpolation) +CTFlowsPlots โ†’ Solutions (semantic accessors only) +``` + +--- + +## Step-by-step implementation + +### Step 0 โ€” Branch + +```bash +git checkout develop && git pull +git checkout -b refactor/integration-result-architecture +``` + +### Step 1 โ€” `src/Solutions/integration_result.jl` (new file) + +Define `abstract type AbstractIntegrationResult` and three accessor stubs with `NotImplemented` errors: +- `final_state(r::AbstractIntegrationResult)` +- `times(r::AbstractIntegrationResult)` +- `evaluate_at(r::AbstractIntegrationResult, t::Real)` + +Full docstrings (TYPEDEF + TYPEDSIGNATURES, @ref cross-refs). + +### Step 2 โ€” `src/Solutions/Solutions.jl` + +- `include` the new `integration_result.jl` (before `vector_field_solution.jl`) +- Remove `import SciMLBase` +- Export: `AbstractIntegrationResult`, `final_state`, `times`, `evaluate_at` +- Keep `export raw` removed (see Step 4) + +### Step 3 โ€” `src/Solutions/building.jl` + +- Change both `build_solution` signatures from `ode_sol::SciMLBase.AbstractODESolution` to `result::AbstractIntegrationResult` +- `StatePointConfig` branch: `final_state(result)` replaces `ode_sol.u[end]` +- `StateTrajectoryConfig` branch: `VectorFieldSolution(result)` (no change to wrapping logic, just different argument type) +- Update docstrings + +### Step 4 โ€” `src/Solutions/vector_field_solution.jl` + +- Change struct: `struct VectorFieldSolution{R<:AbstractIntegrationResult}`, field `result::R` +- **Remove `raw`** entirely: its return type would silently change from `SciMLBase.AbstractODESolution` to `AbstractIntegrationResult`, breaking callers with no clear error. Semantic accessors fully cover its use. +- Update callable: `(sol::VectorFieldSolution)(t::Real) = evaluate_at(sol.result, t)` +- Add `times(sol::VectorFieldSolution) = times(sol.result)` (needed by Plots) +- Update `Base.show` to use `times(sol.result)` and `final_state(sol.result)` +- Update docstrings (remove SciML references) + +### Step 5 โ€” `src/Solutions/Solutions.jl` (export sync) + +- Remove `export raw` (raw is deleted) +- Add `export times` (new convenience delegation on VectorFieldSolution) + +### Step 6 โ€” `src/Integrators/abstract_integrator.jl` + +- Remove ALL three callable stubs `(integrator::AbstractIntegrator)(...)` +- Add two named function stubs with `NotImplemented`: + ```julia + function build_problem(int::AbstractIntegrator, sys, config; variable) + throw(NotImplemented(...)) + end + function solve_problem(int::AbstractIntegrator, prob) + throw(NotImplemented(...)) + end + ``` +- Update `AbstractIntegrator` docstring: contract is now two named functions, not callables +- Full docstrings on both stubs + +### Step 7 โ€” `src/Integrators/Integrators.jl` + +- Export `build_problem`, `solve_problem` (new public contract) + +### Step 8 โ€” `src/CTFlows.jl` + +- Load order must be: `Common โ†’ Data โ†’ Systems โ†’ Integrators โ†’ Solutions โ†’ Flows` +- Move `Solutions` include/using **before** `Flows` +- `Integrators` (with exported `build_problem`/`solve_problem`) must already be loaded when `Flows` calls them; `Solutions` must be loaded before `Flows` references `Solutions.build_solution` + +### Step 9 โ€” `src/Flows/Flows.jl` + +- Add `using ..Solutions` (required for `calling.jl` to call `Solutions.build_solution`) + +### Step 10 โ€” `src/Flows/calling.jl` + +- Remove the three wrapper functions (`build_problem`, `solve_problem`, `build_solution`) +- Replace `call` body with: + ```julia + prob = Integrators.build_problem(int, sys, config; variable=variable) + result = Integrators.solve_problem(int, prob) + return Solutions.build_solution(result, config) + ``` +- Update docstring for `call` + +### Step 11 โ€” `ext/CTFlowsSciML.jl` + +- Remove all three callables `(integ::SciML)(...)` +- Add `SciMLIntegrationResult{S<:SciMLBase.AbstractODESolution} <: Solutions.AbstractIntegrationResult` struct +- Implement `Solutions.final_state`, `Solutions.times`, `Solutions.evaluate_at` on it +- Replace callable implementations with named function implementations: + ```julia + function Integrators.build_problem(int::SciML, sys, config; variable) + # ODEProblem(...) + end + function Integrators.solve_problem(int::SciML, prob) + ode_sol = SciMLBase.solve(prob; Strategies.options_dict(int)...) + return SciMLIntegrationResult(ode_sol) + end + ``` +- Full docstrings + +### Step 12 โ€” `ext/CTFlowsPlots.jl` + +- Replace `Plots.plot(Solutions.raw(sol))` delegation with semantic accessor approach: + ```julia + ts = Solutions.times(sol) + states = reduce(hcat, sol.(ts))' # safe for any state dimension, incl. 1D + Plots.plot(ts, states; kwargs...) + ``` +- `reduce(hcat, ...)` works whether each `sol(t)` returns a scalar-wrapped vector, a vector, etc.; prefer over `stack(...)` for 1D safety +- Same for `plot!` variants +- Update docstrings + +### Step 13 โ€” `test/suite/flows/test_calling.jl` + +- `FakeIntegratorForCalling`: remove third callable and `build_solution_called` field +- Implement `Integrators.build_problem(::FakeIntegratorForCalling, ...)` and `Integrators.solve_problem(::FakeIntegratorForCalling, ...)` as named function extensions (not callables) +- `solve_problem` should return a fake that satisfies `Solutions.AbstractIntegrationResult` +- Update test assertions to match new workflow (verify `build_solution` is NOT called on integrator) + +### Step 14 โ€” `test/suite/solutions/test_building_solutions.jl` + +- Remove `import SciMLBase` +- Replace `FakeODESolution <: SciMLBase.AbstractODESolution` with `FakeIntegrationResult <: Solutions.AbstractIntegrationResult` +- Implement `final_state`, `times`, `evaluate_at` on the fake +- All `build_solution` tests use the new fake โ€” no SciML in this file + +### Step 15 โ€” `test/suite/solutions/test_vector_field_solution.jl` + +- Remove `import SciMLBase` +- Replace `FakeODESolution` with `FakeIntegrationResult <: Solutions.AbstractIntegrationResult` +- Remove all tests for `raw` (deleted) +- Update callable test to use `evaluate_at` semantics +- Update `Base.show` tests accordingly + +### Step 16 โ€” `test/suite/extensions/test_sciml_extension.jl` + +- Replace all three callable forms (`integ(sys, config; variable)`, `integ(prob)`, `integ(ode_sol, sys, config)`) with named function calls (`Integrators.build_problem`, `Integrators.solve_problem`) +- Add tests for `SciMLIntegrationResult`: construction, `final_state`, `times`, `evaluate_at` +- Verify `Integrators.solve_problem` returns a `Solutions.AbstractIntegrationResult` + +### Step 17 โ€” `test/suite/solutions/test_decoupling.jl` (new, priority) + +**Do NOT use `!isdefined(Solutions, :SciMLBase)` as a check** โ€” another test file may have already loaded SciML, making the check unreliable. Use the same pattern as the rest of the test suite: fake subtypes for stub testing, real types (via explicit `using`) for positive testing. + +- **Stub tests** (use fake subtypes โ€” always reliable regardless of what is loaded): + - `struct FakeResult <: Solutions.AbstractIntegrationResult end` at module top-level + - `@test_throws NotImplemented Solutions.final_state(FakeResult())` + - `@test_throws NotImplemented Solutions.times(FakeResult())` + - `@test_throws NotImplemented Solutions.evaluate_at(FakeResult(), 0.0)` + +- **Functional decoupling test** (the real architectural guarantee): + - Define `MockIntegrationResult <: Solutions.AbstractIntegrationResult` โ€” pure Julia, zero SciML imports + - Implement `final_state`, `times`, `evaluate_at` with deterministic test data + - `Solutions.build_solution(mock, sys, StatePointConfig)` โ†’ returns expected state vector + - `Solutions.build_solution(mock, sys, StateTrajectoryConfig)` โ†’ returns `VectorFieldSolution` + - `VectorFieldSolution(mock)` callable: `sol(0.5)` returns expected value + - `Base.show(io, sol)` and `Base.show(io, MIME("text/plain"), sol)` run without error + - If this passes: `Solutions` is functionally decoupled from SciML regardless of what is loaded elsewhere + +- **Symmetrical check**: also verify `Solutions` is decoupled from `Integrators` โ€” the functional test covers this implicitly since `MockIntegrationResult` has no integrator type dependency, but add an explicit assertion `@test !(:Integrators in nameof.(Solutions.usings))` or equivalent if feasible + +### Step 18 โ€” Run tests + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/ctflows_refactor.log +grep -E "Error|Fail|Test Summary" /tmp/ctflows_refactor.log +``` + +--- + +## Files summary + +**New**: `src/Solutions/integration_result.jl`, `test/suite/solutions/test_decoupling.jl` + +**Modified**: +- `src/CTFlows.jl` โ€” load order +- `src/Solutions/Solutions.jl` โ€” imports, exports, includes +- `src/Solutions/building.jl` โ€” signature change +- `src/Solutions/vector_field_solution.jl` โ€” struct, remove `raw`, new accessors +- `src/Integrators/abstract_integrator.jl` โ€” remove callables, add named stubs +- `src/Integrators/Integrators.jl` โ€” export `build_problem`, `solve_problem` +- `src/Flows/Flows.jl` โ€” add `using ..Solutions` +- `src/Flows/calling.jl` โ€” simplify to `call` only +- `ext/CTFlowsSciML.jl` โ€” remove callables, add `SciMLIntegrationResult`, named impls +- `ext/CTFlowsPlots.jl` โ€” semantic accessors +- `test/suite/flows/test_calling.jl` +- `test/suite/solutions/test_building_solutions.jl` +- `test/suite/solutions/test_vector_field_solution.jl` +- `test/suite/extensions/test_sciml_extension.jl` diff --git a/reports/save/v1/simplify-build-integrator.md b/reports/save/v1/simplify-build-integrator.md new file mode 100644 index 00000000..0cc05aec --- /dev/null +++ b/reports/save/v1/simplify-build-integrator.md @@ -0,0 +1,316 @@ +# Simplify `build_integrator` by removing Symbol dispatch (Point 3) + +Remove the unnecessary `id::Symbol` parameter from `build_integrator` and the `Flow` constructor to eliminate defensive code against a hypothetical need. + +--- + +## What changes and why + +**Current state**: `build_integrator(id::Symbol; kwargs...)` dispatches on `:sciml` and throws `IncorrectArgument` for any other symbol. The `Flow` constructor accepts `id::Symbol=:sciml` and forwards it to `build_integrator`. This adds friction without benefit since there is only one integrator today. + +**After refactoring**: Both functions accept only keyword options. `build_integrator` directly returns `SciML(; kwargs...)` without any validation. The API becomes cleaner and future-proof โ€” if a second integrator arrives, dispatch can be reintroduced via type or Symbol with a real use case to justify it. + +**API change**: +- Before: `Flow(vf, :sciml; reltol=1e-8)` +- After: `Flow(vf; reltol=1e-8)` + +--- + +## Files affected + +### Source code +- `src/Integrators/building.jl` โ€” simplify `build_integrator` signature and implementation +- `src/Flows/building.jl` โ€” remove `id::Symbol` parameter from `Flow` constructor + +### Tests +- `test/suite/integrators/test_building_integrators.jl` โ€” remove Symbol dispatch tests, keep export verification +- `test/suite/flows/test_building_flows.jl` โ€” update Flow constructor calls to remove `:sciml` + +### Documentation +- `docs/src/index.md` โ€” update example to remove `:sciml` +- `windsurf/rules/documentation.md` โ€” update examples to remove `:sciml` + +--- + +## Step-by-step implementation + +### Step 0 โ€” Branch + +```bash +git checkout develop && git pull +git checkout -b refactor/simplify-build-integrator +``` + +### Step 1 โ€” `src/Integrators/building.jl` + +Simplify the function signature and implementation: + +```julia +""" +$(TYPEDSIGNATURES) + +Build a `SciML` integrator with the given options. + +# Arguments +- `kwargs...`: Options forwarded to the `SciML` constructor. + +# Returns +- `CTFlows.Integrators.SciML`: The configured integrator. + +# Example +\`\`\`julia +using CTFlows.Integrators + +integrator = Integrators.build_integrator(reltol=1e-8, alg=Tsit5()) +\`\`\` + +See also: [`CTFlows.Integrators.SciML`](@ref), [`CTFlows.Integrators.build_sciml_integrator`](@ref). +""" +function build_integrator(; kwargs...) + return SciML(; kwargs...) +end +``` + +**Changes**: +- Remove `id::Symbol` parameter from signature +- Remove the `if/else` dispatch logic +- Remove the `IncorrectArgument` exception (no longer needed) +- Update docstring to reflect new signature and purpose + +### Step 2 โ€” `src/Flows/building.jl` + +Update the `Flow` constructor to remove the `id` parameter: + +```julia +""" +$(TYPEDSIGNATURES) + +High-level constructor for `Flow` from vector field data. + +This constructor builds a complete flow by: +1. Building a `VectorFieldSystem` from the vector field data +2. Building a `SciML` integrator with the given options +3. Routing options through the integrator's CTSolvers strategy +4. Combining them into a callable `Flow` + +# Arguments +- `data::CTFlows.Data.VectorField`: The vector field defining the system dynamics. +- `opts...`: Keyword options passed to the integrator's strategy. + +# Returns +- `CTFlows.Flows.Flow`: The complete flow ready for integration. + +# Example +\`\`\`julia +using CTFlows.Data, CTFlows.Flows, CTFlows.Common + +vf = Data.VectorField((t, x, v) -> x, Common.Autonomous(), Common.Fixed()) +flow = Flows.Flow(vf; reltol=1e-8) +\`\`\` + +See also: [`CTFlows.Flows.Flow`](@ref), [`CTFlows.Systems.build_system`](@ref), [`CTFlows.Integrators.build_integrator`](@ref). +""" +function Flow(data::Data.VectorField; opts...) + system = Systems.build_system(data) + integrator = Integrators.build_integrator(; opts...) + return Flow(system, integrator) +end +``` + +**Changes**: +- Remove `id::Symbol=:sciml` parameter from signature +- Call `Integrators.build_integrator(; opts...)` without the `id` argument +- Update docstring to remove references to integrator identifier +- Update example to remove `:sciml` + +### Step 3 โ€” `test/suite/integrators/test_building_integrators.jl` + +Remove Symbol dispatch tests, keep export verification: + +```julia +function test_building_integrators() + Test.@testset "Integrator Building Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - build_integrator + # ==================================================================== + + Test.@testset "build_integrator" begin + + Test.@testset "builds SciML integrator" begin + # This will throw ExtensionError if CTFlowsSciML is not loaded, + # but at least it calls the right function + result = Integrators.build_integrator() + Test.@test result isa Integrators.SciML + end + + Test.@testset "forwards keyword options" begin + result = Integrators.build_integrator(reltol=1e-10) + Test.@test result isa Integrators.SciML + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "build_integrator is exported" begin + Test.@test isdefined(Integrators, :build_integrator) + end + end + end +end +``` + +**Changes**: +- Remove test for "valid id :sciml" (no longer relevant) +- Remove test for "unknown id throws IncorrectArgument" (no longer throws) +- Remove test for "error message for unknown id" (no longer throws) +- Add test for "forwards keyword options" (verifies kwargs forwarding) +- Keep export verification test + +### Step 4 โ€” `test/suite/flows/test_building_flows.jl` + +Update Flow constructor calls to remove `:sciml`: + +```julia +Test.@testset "Flow constructor from VectorField" begin + Test.@testset "default constructor" begin + vf = Data.VectorField(x -> -x; autonomous=true, variable=false) + flow = Flows.Flow(vf) + + Test.@test flow isa Flows.Flow + Test.@test flow isa Flows.AbstractFlow + Test.@test flow.system isa Systems.VectorFieldSystem + Test.@test flow.integrator isa Integrators.AbstractIntegrator + end + + Test.@testset "with keyword options" begin + vf = Data.VectorField(x -> -x; autonomous=true, variable=false) + flow = Flows.Flow(vf; reltol=1e-10) + + Test.@test flow isa Flows.Flow + Test.@test flow.integrator isa Integrators.AbstractIntegrator + end +end +``` + +**Changes**: +- Rename test from "default integrator id (:sciml)" to "default constructor" +- Remove test for "explicit integrator id (:sciml)" (no longer relevant) +- Update "with keyword options" test to remove `:sciml` argument +- All other tests remain unchanged (they already use the default constructor) + +### Step 5 โ€” `docs/src/index.md` + +Update the API example: + +```julia +# Build a flow from vector field data +using CTFlows.Data, CTFlows.Flows, CTFlows.Common + +vf = Data.VectorField((t, x, v) -> x, Autonomous(), Fixed()) +flow = Flows.Flow(vf; reltol=1e-8) + +# Integrate using a configuration +config = Common.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) +sol = Flows.call(flow, config) + +# Or point-to-point integration +config = Common.StatePointConfig((0.0, 1.0), [1.0, 0.0]) +final_state = Flows.call(flow, config) +``` + +**Changes**: +- Change `Flow(vf, :sciml; reltol=1e-8)` to `Flow(vf; reltol=1e-8)` + +### Step 6 โ€” `windsurf/rules/documentation.md` + +Update examples to remove `:sciml`: + +Find and replace all occurrences: +- `Flows.Flow(vf, :sciml)` โ†’ `Flows.Flow(vf)` +- `Flows.Flow(vf, :sciml; ...)` โ†’ `Flows.Flow(vf; ...)` + +**Changes**: +- Update any examples showing the old API +- Ensure consistency with the new simplified API + +--- + +## Testing + +### Run affected test suites + +```bash +# Test integrators +julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/integrators/test_building_integrators"])' + +# Test flows +julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/flows/test_building_flows"])' +``` + +### Run full test suite + +```bash +julia --project=@. -e 'using Pkg; Pkg.test("CTFlows")' +``` + +### Verify documentation builds + +```bash +cd docs +julia --project make.jl +``` + +--- + +## Rollback plan + +If issues arise, revert the changes: + +```bash +git checkout develop +git branch -D refactor/simplify-build-integrator +``` + +Or if already committed: + +```bash +git revert <commit-hash> +``` + +--- + +## Future extensibility + +If a second integrator is added in the future, the Symbol dispatch can be reintroduced with a real use case: + +```julia +# Future: multiple integrators +function build_integrator(id::Symbol=:sciml; kwargs...) + if id === :sciml + return SciML(; kwargs...) + elseif id === :custom + return CustomIntegrator(; kwargs...) + else + throw(IncorrectArgument(...)) + end +end +``` + +Or better, use type-based dispatch: + +```julia +# Future: type-based dispatch (preferred) +function build_integrator(::Type{SciML}; kwargs...) + return SciML(; kwargs...) +end + +function build_integrator(::Type{CustomIntegrator}; kwargs...) + return CustomIntegrator(; kwargs...) +end +``` + +The current simplification removes premature optimization and makes the API cleaner for the current single-integrator case. diff --git a/save/docs/Project.toml b/save/docs/Project.toml new file mode 100644 index 00000000..308212e8 --- /dev/null +++ b/save/docs/Project.toml @@ -0,0 +1,12 @@ +[deps] +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" + +[compat] +CTModels = "0.10" +Documenter = "1" +DocumenterMermaid = "0.2" +OrdinaryDiffEq = "6" +julia = "1.10" diff --git a/save/docs/build/.documenter-siteinfo.json b/save/docs/build/.documenter-siteinfo.json new file mode 100644 index 00000000..b14fdb34 --- /dev/null +++ b/save/docs/build/.documenter-siteinfo.json @@ -0,0 +1 @@ +{"documenter":{"julia_version":"1.12.1","generation_timestamp":"2026-02-13T18:27:26","documenter_version":"1.16.1"}} \ No newline at end of file diff --git a/save/docs/build/assets/documenter.js b/save/docs/build/assets/documenter.js new file mode 100644 index 00000000..95e87c4c --- /dev/null +++ b/save/docs/build/assets/documenter.js @@ -0,0 +1,1376 @@ +// Generated by Documenter.jl +requirejs.config({ + paths: { + 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min', + 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min', + 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min', + 'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min', + 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min', + 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min', + 'katex': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min', + 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min', + 'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia-repl.min', + }, + shim: { + "highlight-julia": { + "deps": [ + "highlight" + ] + }, + "katex-auto-render": { + "deps": [ + "katex" + ] + }, + "headroom-jquery": { + "deps": [ + "jquery", + "headroom" + ] + }, + "highlight-julia-repl": { + "deps": [ + "highlight" + ] + } +}}); +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'katex', 'katex-auto-render'], function($, katex, renderMathInElement) { +$(document).ready(function() { + renderMathInElement( + document.body, + { + "delimiters": [ + { + "left": "$", + "right": "$", + "display": false + }, + { + "left": "$$", + "right": "$$", + "display": true + }, + { + "left": "\\[", + "right": "\\]", + "display": true + } + ] +} + ); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($) { +$(document).ready(function() { + hljs.highlightAll(); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +/////////////////////////////////// + +// to open and scroll to +function openTarget() { + const hash = decodeURIComponent(location.hash.substring(1)); + if (hash) { + const target = document.getElementById(hash); + if (target) { + const details = target.closest("details"); + if (details) details.open = true; + } + } +} +openTarget(); // onload +window.addEventListener("hashchange", openTarget); +window.addEventListener("load", openTarget); + +////////////////////////////////////// +// for the global expand/collapse butter + +function accordion() { + document.body + .querySelectorAll("details.docstring") + .forEach((e) => e.setAttribute("open", "true")); +} + +function noccordion() { + document.body + .querySelectorAll("details.docstring") + .forEach((e) => e.removeAttribute("open")); +} + +function expandAll() { + let me = document.getElementById("documenter-article-toggle-button"); + me.setAttribute("open", "true"); + $(me).removeClass("fa-chevron-down").addClass("fa-chevron-up"); + $(me).prop("title", "Collapse all docstrings"); + accordion(); +} + +function collapseAll() { + let me = document.getElementById("documenter-article-toggle-button"); + me.removeAttribute("open"); + $(me).removeClass("fa-chevron-up").addClass("fa-chevron-down"); + $(me).prop("title", "Expand all docstrings"); + noccordion(); +} + +$(document).on("click", ".docs-article-toggle-button", function () { + var isExpanded = this.hasAttribute("open"); + if (isExpanded) { + collapseAll(); + isExpanded = false; + } else { + expandAll(); + isExpanded = true; + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require([], function() { +function addCopyButtonCallbacks() { + for (const el of document.getElementsByTagName("pre")) { + const button = document.createElement("button"); + button.classList.add("copy-button", "fa-solid", "fa-copy"); + button.setAttribute("aria-label", "Copy this code block"); + button.setAttribute("title", "Copy"); + + el.appendChild(button); + + const success = function () { + button.classList.add("success", "fa-check"); + button.classList.remove("fa-copy"); + }; + + const failure = function () { + button.classList.add("error", "fa-xmark"); + button.classList.remove("fa-copy"); + }; + + button.addEventListener("click", function () { + copyToClipboard(el.innerText).then(success, failure); + + setTimeout(function () { + button.classList.add("fa-copy"); + button.classList.remove("success", "fa-check", "fa-xmark"); + }, 5000); + }); + } +} + +function copyToClipboard(text) { + // clipboard API is only available in secure contexts + if (window.navigator && window.navigator.clipboard) { + return window.navigator.clipboard.writeText(text); + } else { + return new Promise(function (resolve, reject) { + try { + const el = document.createElement("textarea"); + el.textContent = text; + el.style.position = "fixed"; + el.style.opacity = 0; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + + resolve(); + } catch (err) { + reject(err); + } finally { + document.body.removeChild(el); + } + }); + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtonCallbacks); +} else { + addCopyButtonCallbacks(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { +$(document).ready(function () { + $(".footnote-ref").hover( + function () { + var id = $(this).attr("href"); + var footnoteContent = $(id).clone().find("a").remove().end().html(); + + var $preview = $(this).next(".footnote-preview"); + + $preview.html(footnoteContent).css({ + display: "block", + left: "50%", + transform: "translateX(-50%)", + }); + + repositionPreview($preview, $(this)); + }, + function () { + var $preview = $(this).next(".footnote-preview"); + $preview.css({ + display: "none", + left: "", + transform: "", + "--arrow-left": "", + }); + }, + ); + + function repositionPreview($preview, $ref) { + var previewRect = $preview[0].getBoundingClientRect(); + var refRect = $ref[0].getBoundingClientRect(); + var viewportWidth = $(window).width(); + + if (previewRect.right > viewportWidth) { + var excessRight = previewRect.right - viewportWidth; + $preview.css("left", `calc(50% - ${excessRight + 10}px)`); + } else if (previewRect.left < 0) { + var excessLeft = 0 - previewRect.left; + $preview.css("left", `calc(50% + ${excessLeft + 10}px)`); + } + + var newPreviewRect = $preview[0].getBoundingClientRect(); + + var arrowLeft = refRect.left + refRect.width / 2 - newPreviewRect.left; + + $preview.css("--arrow-left", arrowLeft + "px"); + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) { + +// Manages the top navigation bar (hides it when the user starts scrolling down on the +// mobile). +window.Headroom = Headroom; // work around buggy module loading? +$(document).ready(function () { + $("#documenter .docs-navbar").headroom({ + tolerance: { up: 10, down: 10 }, + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +$(document).ready(function () { + let meta = $("div[data-docstringscollapsed]").data(); + if (!meta?.docstringscollapsed) { + $("#documenter-article-toggle-button").trigger({ + type: "click", + }); + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +/* +To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc + +PSEUDOCODE: + +Searching happens automatically as the user types or adjusts the selected filters. +To preserve responsiveness, as much as possible of the slow parts of the search are done +in a web worker. Searching and result generation are done in the worker, and filtering and +DOM updates are done in the main thread. The filters are in the main thread as they should +be very quick to apply. This lets filters be changed without re-searching with minisearch +(which is possible even if filtering is on the worker thread) and also lets filters be +changed _while_ the worker is searching and without message passing (neither of which are +possible if filtering is on the worker thread) + +SEARCH WORKER: + +Import minisearch + +Build index + +On message from main thread + run search + find the first 200 unique results from each category, and compute their divs for display + note that this is necessary and sufficient information for the main thread to find the + first 200 unique results from any given filter set + post results to main thread + +MAIN: + +Launch worker + +Declare nonconstant globals (worker_is_running, last_search_text, unfiltered_results) + +On text update + if worker is not running, launch_search() + +launch_search + set worker_is_running to true, set last_search_text to the search text + post the search query to worker + +on message from worker + if last_search_text is not the same as the text in the search field, + the latest search result is not reflective of the latest search query, so update again + launch_search() + otherwise + set worker_is_running to false + + regardless, display the new search results to the user + save the unfiltered_results as a global + update_search() + +on filter click + adjust the filter selection + update_search() + +update_search + apply search filters by looping through the unfiltered_results and finding the first 200 + unique results that match the filters + + Update the DOM +*/ + +/////// SEARCH WORKER /////// + +function worker_function(documenterSearchIndex, documenterBaseURL, filters) { + importScripts( + "https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min.js", + ); + + let data = documenterSearchIndex.map((x, key) => { + x["id"] = key; // minisearch requires a unique for each object + return x; + }); + + // list below is the lunr 2.1.3 list minus the intersect with names(Base) + // (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) + // ideally we'd just filter the original list but it's not available as a variable + const stopWords = new Set([ + "a", + "able", + "about", + "across", + "after", + "almost", + "also", + "am", + "among", + "an", + "and", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "does", + "either", + "ever", + "every", + "from", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "into", + "it", + "its", + "just", + "least", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "who", + "whom", + "why", + "will", + "would", + "yet", + "you", + "your", + ]); + + let index = new MiniSearch({ + fields: ["title", "text"], // fields to index for full-text search + storeFields: ["location", "title", "text", "category", "page"], // fields to return with results + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + // custom trimmer that doesn't strip special characters `@!+-*/^&|%<>=:.` which are used in julia macro and function names. + word = word + .replace(/^[^a-zA-Z0-9@!+\-/*^&%|<>._=:]+/, "") + .replace(/[^a-zA-Z0-9@!+\-/*^&%|<>._=:]+$/, ""); + + word = word.toLowerCase(); + } + + return word ?? null; + }, + // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not + // find anything if searching for "add!", only for the entire qualification + tokenize: (string) => { + const tokens = []; + let remaining = string; + + // julia specific patterns + const patterns = [ + // Module qualified names (e.g., Base.sort, Module.Submodule. function) + /\b[A-Za-z0-9_1*(?:\.[A-Z][A-Za-z0-9_1*)*\.[a-z_][A-Za-z0-9_!]*\b/g, + // Macro calls (e.g., @time, @async) + /@[A-Za-z0-9_]*/g, + // Type parameters (e.g., Array{T,N}, Vector{Int}) + /\b[A-Za-z0-9_]*\{[^}]+\}/g, + // Function names with module qualification (e.g., Base.+, Base.:^) + /\b[A-Za-z0-9_]*\.:[A-Za-z0-9_!+\-*/^&|%<>=.]+/g, + // Operators as complete tokens (e.g., !=, aรฃ, ||, ^, .=, โ†’) + /[!<>=+\-*/^&|%:.]+/g, + // Function signatures with type annotations (e.g., f(x::Int)) + /\b[A-Za-z0-9_!]*\([^)]*::[^)]*\)/g, + // Numbers (integers, floats, scientific notation) + /\b\d+(?:\.\d+)? (?:[eE][+-]?\d+)?\b/g, + ]; + + // apply patterns in order of specificity + for (const pattern of patterns) { + pattern.lastIndex = 0; //reset regex state + let match; + while ((match = pattern.exec(remaining)) != null) { + const token = match[0].trim(); + if (token && !tokens.includes(token)) { + tokens.push(token); + } + } + } + + // splitting the content if something remains + const basicTokens = remaining + .split(/[\s\-,;()[\]{}]+/) + .filter((t) => t.trim()); + for (const token of basicTokens) { + if (token && !tokens.includes(token)) { + tokens.push(token); + } + } + + return tokens.filter((token) => token.length > 0); + }, + // options which will be applied during the search + searchOptions: { + prefix: true, + boost: { title: 100 }, + fuzzy: 2, + }, + }); + + index.addAll(data); + + /** + * Used to map characters to HTML entities. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const htmlEscapes = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }; + + /** + * Used to match HTML entities and HTML characters. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const reUnescapedHtml = /[&<>"']/g; + const reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + + /** + * Escape function from lodash + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + function escape(string) { + return string && reHasUnescapedHtml.test(string) + ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr]) + : string || ""; + } + + /** + * RegX escape function from MDN + * Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + } + + /** + * Make the result component given a minisearch result data object and the value + * of the search input as queryString. To view the result object structure, refer: + * https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult + * + * @param {object} result + * @param {string} querystring + * @returns string + */ + function make_search_result(result, querystring) { + let search_divider = `<div class="search-divider w-100"></div>`; + let display_link = + result.location.slice(Math.max(0), Math.min(50, result.location.length)) + + (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div + + if (result.page !== "") { + display_link += ` (${result.page})`; + } + searchstring = escapeRegExp(querystring); + let textindex = new RegExp(`${searchstring}`, "i").exec(result.text); + let text = + textindex !== null + ? result.text.slice( + Math.max(textindex.index - 100, 0), + Math.min( + textindex.index + querystring.length + 100, + result.text.length, + ), + ) + : ""; // cut-off text before and after from the match + + text = text.length ? escape(text) : ""; + + let display_result = text.length + ? "..." + + text.replace( + new RegExp(`${escape(searchstring)}`, "i"), // For first occurrence + '<span class="search-result-highlight py-1">$&</span>', + ) + + "..." + : ""; // highlights the match + + let in_code = false; + if (!["page", "section"].includes(result.category.toLowerCase())) { + in_code = true; + } + + // We encode the full url to escape some special characters which can lead to broken links + let result_div = ` + <a href="${encodeURI( + documenterBaseURL + "/" + result.location, + )}" class="search-result-link w-100 is-flex is-flex-direction-column gap-2 px-4 py-2"> + <div class="w-100 is-flex is-flex-wrap-wrap is-justify-content-space-between is-align-items-flex-start"> + <div class="search-result-title has-text-weight-bold ${ + in_code ? "search-result-code-title" : "" + }">${escape(result.title)}</div> + <div class="property-search-result-badge">${result.category}</div> + </div> + <p> + ${display_result} + </p> + <div + class="has-text-left" + style="font-size: smaller;" + title="${result.location}" + > + <i class="fas fa-link"></i> ${display_link} + </div> + </a> + ${search_divider} + `; + + return result_div; + } + + function calculateCustomScore(result, query) { + const titleLower = result.title.toLowerCase(); + const queryLower = query.toLowerCase(); + + // Tier 1 : Exact title match + if (titleLower == queryLower) { + return 10000 + result.score; + } + + // Tier 2 : Title contains exact query + if (titleLower.includes(queryLower)) { + const position = titleLower.indexOf(queryLower); + // prefer matches at the beginning + return 5000 + result.score - position * 10; + } + + // Tier 3 : All query words in title + const queryWords = queryLower.trim().split(/\s+/); + const titleWords = titleLower.trim().split(/\s+/); + const allWordsInTitle = queryWords.every((qw) => + titleWords.some((tw) => tw.includes(qw)), + ); + if (allWordsInTitle) { + return 2000 + result.score; + } + + return result.score; + } + + self.onmessage = function (e) { + let query = e.data; + let results = index.search(query, { + filter: (result) => { + // Only return relevant results + return result.score >= 1; + }, + combineWith: "AND", + }); + + // calculate custom scores for all results + results = results.map((result) => ({ + ...result, + customScore: calculateCustomScore(result, query), + })); + + // sort by custom score in descending order + results.sort((a, b) => b.customScore - a.customScore); + + // Pre-filter to deduplicate and limit to 200 per category to the extent + // possible without knowing what the filters are. + let filtered_results = []; + let counts = {}; + for (let filter of filters) { + counts[filter] = 0; + } + let present = {}; + + for (let result of results) { + cat = result.category; + cnt = counts[cat]; + if (cnt < 200) { + id = cat + "---" + result.location; + if (present[id]) { + continue; + } + present[id] = true; + filtered_results.push({ + location: result.location, + category: cat, + div: make_search_result(result, query), + }); + } + } + + postMessage(filtered_results); + }; +} + +/////// SEARCH MAIN /////// + +function runSearchMainCode() { + // `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript! + const filters = [ + ...new Set(documenterSearchIndex["docs"].map((x) => x.category)), + ]; + const worker_str = + "(" + + worker_function.toString() + + ")(" + + JSON.stringify(documenterSearchIndex["docs"]) + + "," + + JSON.stringify(documenterBaseURL) + + "," + + JSON.stringify(filters) + + ")"; + const worker_blob = new Blob([worker_str], { type: "text/javascript" }); + const worker = new Worker(URL.createObjectURL(worker_blob)); + + // Whether the worker is currently handling a search. This is a boolean + // as the worker only ever handles 1 or 0 searches at a time. + var worker_is_running = false; + + // The last search text that was sent to the worker. This is used to determine + // if the worker should be launched again when it reports back results. + var last_search_text = ""; + + // The results of the last search. This, in combination with the state of the filters + // in the DOM, is used compute the results to display on calls to update_search. + var unfiltered_results = []; + + // Which filter is currently selected + var selected_filter = ""; + + document.addEventListener("reset-filter", function () { + selected_filter = ""; + update_search(); + }); + + //update the url with search query + function updateSearchURL(query) { + const url = new URL(window.location); + + if (query && query.trim() !== "") { + url.searchParams.set("q", query); + } else { + // remove the 'q' param if it exists + if (url.searchParams.has("q")) { + url.searchParams.delete("q"); + } + } + + // Add or remove the filter parameter based on selected_filter + if (selected_filter && selected_filter.trim() !== "") { + url.searchParams.set("filter", selected_filter); + } else { + // remove the 'filter' param if it exists + if (url.searchParams.has("filter")) { + url.searchParams.delete("filter"); + } + } + + // Only update history if there are parameters, otherwise use the base URL + if (url.search) { + window.history.replaceState({}, "", url); + } else { + window.history.replaceState({}, "", url.pathname + url.hash); + } + } + + $(document).on("input", ".documenter-search-input", function (event) { + if (!worker_is_running) { + launch_search(); + } + }); + + function launch_search() { + worker_is_running = true; + last_search_text = $(".documenter-search-input").val(); + updateSearchURL(last_search_text); + worker.postMessage(last_search_text); + } + + worker.onmessage = function (e) { + if (last_search_text !== $(".documenter-search-input").val()) { + launch_search(); + } else { + worker_is_running = false; + } + + unfiltered_results = e.data; + update_search(); + }; + + $(document).on("click", ".search-filter", function () { + let search_input = $(".documenter-search-input"); + let cursor_position = search_input[0].selectionStart; + + if ($(this).hasClass("search-filter-selected")) { + selected_filter = ""; + } else { + selected_filter = $(this).text().toLowerCase(); + } + + // This updates search results and toggles classes for UI: + update_search(); + + search_input.focus(); + search_input.setSelectionRange(cursor_position, cursor_position); + }); + + /** + * Make/Update the search component + */ + function update_search() { + let querystring = $(".documenter-search-input").val(); + updateSearchURL(querystring); + + if (querystring.trim()) { + if (selected_filter == "") { + results = unfiltered_results; + } else { + results = unfiltered_results.filter((result) => { + return selected_filter == result.category.toLowerCase(); + }); + } + + let search_result_container = ``; + let modal_filters = make_modal_body_filters(); + let search_divider = `<div class="search-divider w-100"></div>`; + + if (results.length) { + let links = []; + let count = 0; + let search_results = ""; + + for (var i = 0, n = results.length; i < n && count < 200; ++i) { + let result = results[i]; + if (result.location && !links.includes(result.location)) { + search_results += result.div; + count++; + links.push(result.location); + } + } + + if (count == 1) { + count_str = "1 result"; + } else if (count == 200) { + count_str = "200+ results"; + } else { + count_str = count + " results"; + } + let result_count = `<div class="is-size-6">${count_str}</div>`; + + search_result_container = ` + <div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start"> + ${modal_filters} + ${search_divider} + ${result_count} + <div class="is-clipped w-100 is-flex is-flex-direction-column gap-2 is-align-items-flex-start has-text-justified mt-1"> + ${search_results} + </div> + </div> + `; + } else { + search_result_container = ` + <div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start"> + ${modal_filters} + ${search_divider} + <div class="is-size-6">0 result(s)</div> + </div> + <div class="has-text-centered my-5 py-5">No result found!</div> + `; + } + + if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").removeClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(search_result_container); + } else { + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(` + <div class="has-text-centered my-5 py-5">Type something to get started!</div> + `); + } + } + + //url param checking + function checkURLForSearch() { + const urlParams = new URLSearchParams(window.location.search); + const searchQuery = urlParams.get("q"); + const filterParam = urlParams.get("filter"); + + // Set the selected filter if present in URL + if (filterParam) { + selected_filter = filterParam.toLowerCase(); + } + + // Trigger input event if there's a search query to perform the search + if (searchQuery) { + $(".documenter-search-input").val(searchQuery).trigger("input"); + } + } + setTimeout(checkURLForSearch, 100); + + /** + * Make the modal filter html + * + * @returns string + */ + function make_modal_body_filters() { + let str = filters + .map((val) => { + if (selected_filter == val.toLowerCase()) { + return `<a href="javascript:;" class="search-filter search-filter-selected"><span>${val}</span></a>`; + } else { + return `<a href="javascript:;" class="search-filter"><span>${val}</span></a>`; + } + }) + .join(""); + + return ` + <div class="is-flex gap-2 is-flex-wrap-wrap is-justify-content-flex-start is-align-items-center search-filters"> + <span class="is-size-6">Filters:</span> + ${str} + </div>`; + } +} + +function waitUntilSearchIndexAvailable() { + // It is possible that the documenter.js script runs before the page + // has finished loading and documenterSearchIndex gets defined. + // So we need to wait until the search index actually loads before setting + // up all the search-related stuff. + if ( + typeof documenterSearchIndex !== "undefined" && + typeof $ !== "undefined" + ) { + runSearchMainCode(); + } else { + console.warn("Search Index or jQuery not available, waiting"); + setTimeout(waitUntilSearchIndexAvailable, 100); + } +} + +// The actual entry point to the search code +waitUntilSearchIndexAvailable(); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Modal settings dialog +$(document).ready(function () { + var settings = $("#documenter-settings"); + $("#documenter-settings-button").click(function () { + settings.toggleClass("is-active"); + }); + // Close the dialog if X is clicked + $("#documenter-settings button.delete").click(function () { + settings.removeClass("is-active"); + }); + // Close dialog if ESC is pressed + $(document).keyup(function (e) { + if (e.keyCode == 27) settings.removeClass("is-active"); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +$(document).ready(function () { + let search_modal_header = ` + <header class="modal-card-head gap-2 is-align-items-center is-justify-content-space-between w-100 px-3"> + <div class="field mb-0 w-100"> + <p class="control has-icons-right"> + <input class="input documenter-search-input" type="text" placeholder="Search" /> + <span class="icon is-small is-right has-text-primary-dark gap-2"> + <i class="fas fa-link link-icon is-clickable"></i> + <i class="fas fa-magnifying-glass mr-4"></i> + </span> + </p> + </div> + <div class="icon is-size-4 is-clickable close-search-modal"> + <i class="fas fa-times"></i> + </div> + </header> + `; + + let initial_search_body = ` + <div class="has-text-centered my-5 py-5">Type something to get started!</div> + `; + + let search_modal_footer = ` + <footer class="modal-card-foot is-flex is-justify-content-space-between is-align-items-center"> + <div class="is-flex gap-3 is-flex-wrap-wrap"> + <span> + <kbd class="search-modal-key-hints">Ctrl</kbd> + + <kbd class="search-modal-key-hints">/</kbd> to search + </span> + <span> <kbd class="search-modal-key-hints">esc</kbd> to close </span> + </div> + <div class="is-flex gap-3 is-flex-wrap-wrap"> + <span> + <kbd class="search-modal-key-hints">โ†‘</kbd> + <kbd class="search-modal-key-hints">โ†“</kbd> to navigate + </span> + <span> <kbd class="search-modal-key-hints">Enter</kbd> to select </span> + </div> + </footer> + `; + + $(document.body).append( + ` + <div class="modal" id="search-modal"> + <div class="modal-background"></div> + <div class="modal-card search-min-width-50 search-min-height-100 is-justify-content-center"> + ${search_modal_header} + <section class="modal-card-body is-flex is-flex-direction-column is-justify-content-center gap-4 search-modal-card-body"> + ${initial_search_body} + </section> + ${search_modal_footer} + </div> + </div> + `, + ); + + function checkURLForSearch() { + const urlParams = new URLSearchParams(window.location.search); + const searchQuery = urlParams.get("q"); + + if (searchQuery) { + //only if there is a search query, open the modal + openModal(); + } + } + + //this function will be called whenever the page will load + checkURLForSearch(); + + document.querySelector(".docs-search-query").addEventListener("click", () => { + openModal(); + }); + + document + .querySelector(".close-search-modal") + .addEventListener("click", () => { + closeModal(); + }); + + $(document).on("click", ".search-result-link", function () { + closeModal(); + }); + + document.addEventListener("keydown", (event) => { + if ((event.ctrlKey || event.metaKey) && event.key === "/") { + openModal(); + } else if (event.key === "Escape") { + closeModal(); + } else if ( + document.querySelector("#search-modal")?.classList.contains("is-active") + ) { + const searchResults = document.querySelectorAll(".search-result-link"); + + if (event.key === "ArrowDown") { + event.preventDefault(); + if (searchResults.length > 0) { + const currentFocused = document.activeElement; + const currentIndex = + Array.from(searchResults).indexOf(currentFocused); + const nextIndex = + currentIndex < searchResults.length - 1 ? currentIndex + 1 : 0; + searchResults[nextIndex].focus(); + } + } else if (event.key === "ArrowUp") { + event.preventDefault(); + if (searchResults.length > 0) { + const currentFocused = document.activeElement; + const currentIndex = + Array.from(searchResults).indexOf(currentFocused); + const prevIndex = + currentIndex > 0 ? currentIndex - 1 : searchResults.length - 1; + searchResults[prevIndex].focus(); + } + } + } + }); + + //event listener for the link icon to copy the URL + $(document).on("click", ".link-icon", function () { + const currentUrl = window.location.href; + + navigator.clipboard + .writeText(currentUrl) + .then(() => { + const $linkIcon = $(this); + $linkIcon.removeClass("fa-link").addClass("fa-check"); + + setTimeout(() => { + $linkIcon.removeClass("fa-check").addClass("fa-link"); + }, 1000); + }) + .catch((err) => { + console.error("Failed to copy URL: ", err); + }); + }); + + // Functions to open and close a modal + function openModal() { + let searchModal = document.querySelector("#search-modal"); + + searchModal.classList.add("is-active"); + document.querySelector(".documenter-search-input").focus(); + } + + function closeModal() { + let searchModal = document.querySelector("#search-modal"); + let initial_search_body = ` + <div class="has-text-centered my-5 py-5">Type something to get started!</div> + `; + + $(".documenter-search-input").val(""); + $(".search-modal-card-body").html(initial_search_body); + + document.dispatchEvent(new CustomEvent("reset-filter")); + + searchModal.classList.remove("is-active"); + document.querySelector(".documenter-search-input").blur(); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + } + + document + .querySelector("#search-modal .modal-background") + .addEventListener("click", () => { + closeModal(); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Manages the showing and hiding of the sidebar. +$(document).ready(function () { + var sidebar = $("#documenter > .docs-sidebar"); + var sidebar_button = $("#documenter-sidebar-button"); + sidebar_button.click(function (ev) { + ev.preventDefault(); + sidebar.toggleClass("visible"); + if (sidebar.hasClass("visible")) { + // Makes sure that the current menu item is visible in the sidebar. + $("#documenter .docs-menu a.is-active").focus(); + } + }); + $("#documenter > .docs-main").bind("click", function (ev) { + if ($(ev.target).is(sidebar_button)) { + return; + } + if (sidebar.hasClass("visible")) { + sidebar.removeClass("visible"); + } + }); +}); + +// Resizes the package name / sitename in the sidebar if it is too wide. +// Inspired by: https://github.com/davatron5000/FitText.js +$(document).ready(function () { + e = $("#documenter .docs-autofit"); + function resize() { + var L = parseInt(e.css("max-width"), 10); + var L0 = e.width(); + if (L0 > L) { + var h0 = parseInt(e.css("font-size"), 10); + e.css("font-size", (L * h0) / L0); + // TODO: make sure it survives resizes? + } + } + // call once and then register events + resize(); + $(window).resize(resize); + $(window).on("orientationchange", resize); +}); + +// Scroll the navigation bar to the currently selected menu item +$(document).ready(function () { + var sidebar = $("#documenter .docs-menu").get(0); + var active = $("#documenter .docs-menu .is-active").get(0); + if (typeof active !== "undefined") { + sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15; + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Theme picker setup +$(document).ready(function () { + // onchange callback + $("#documenter-themepicker").change(function themepick_callback(ev) { + var themename = $("#documenter-themepicker option:selected").attr("value"); + if (themename === "auto") { + // set_theme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + window.localStorage.removeItem("documenter-theme"); + } else { + // set_theme(themename); + window.localStorage.setItem("documenter-theme", themename); + } + // We re-use the global function from themeswap.js to actually do the swapping. + set_theme_from_local_storage(); + }); + + // Make sure that the themepicker displays the correct theme when the theme is retrieved + // from localStorage + if (typeof window.localStorage !== "undefined") { + var theme = window.localStorage.getItem("documenter-theme"); + if (theme !== null) { + $("#documenter-themepicker option").each(function (i, e) { + e.selected = e.value === theme; + }); + } + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// update the version selector with info from the siteinfo.js and ../versions.js files +$(document).ready(function () { + // If the version selector is disabled with DOCUMENTER_VERSION_SELECTOR_DISABLED in the + // siteinfo.js file, we just return immediately and not display the version selector. + if ( + typeof DOCUMENTER_VERSION_SELECTOR_DISABLED === "boolean" && + DOCUMENTER_VERSION_SELECTOR_DISABLED + ) { + return; + } + + var version_selector = $("#documenter .docs-version-selector"); + var version_selector_select = $("#documenter .docs-version-selector select"); + + version_selector_select.change(function (x) { + target_href = version_selector_select + .children("option:selected") + .get(0).value; + + // if the target is just "#", don't navigate (it's the current version) + if (target_href === "#") { + return; + } + + // try to stay on the same page when switching versions + // get the current page path relative to the version root + var current_page = window.location.pathname; + + // resolve the documenterBaseURL to an absolute path + // documenterBaseURL is a relative path (usually "."), so we need to resolve it + var base_url_absolute = new URL(documenterBaseURL, window.location.href) + .pathname; + if (!base_url_absolute.endsWith("/")) { + base_url_absolute = base_url_absolute + "/"; + } + + // extract the page path after the version directory + // e.g., if we're on /stable/man/guide.html, we want "man/guide.html" + var page_path = ""; + if (current_page.startsWith(base_url_absolute)) { + page_path = current_page.substring(base_url_absolute.length); + } + + // construct the target URL with the same page path + var target_url = target_href; + if (page_path && page_path !== "" && page_path !== "index.html") { + // remove trailing slash from target_href if present + if (target_url.endsWith("/")) { + target_url = target_url.slice(0, -1); + } + target_url = target_url + "/" + page_path; + } + + // check if the target page exists, fallback to homepage if it doesn't + fetch(target_url, { method: "HEAD" }) + .then(function (response) { + if (response.ok) { + window.location.href = target_url; + } else { + // page doesn't exist in the target version, go to homepage + window.location.href = target_href; + } + }) + .catch(function (error) { + // network error or other failure - use homepage + window.location.href = target_href; + }); + }); + + // add the current version to the selector based on siteinfo.js, but only if the selector is empty + if ( + typeof DOCUMENTER_CURRENT_VERSION !== "undefined" && + $("#version-selector > option").length == 0 + ) { + var option = $( + "<option value='#' selected='selected'>" + + DOCUMENTER_CURRENT_VERSION + + "</option>", + ); + version_selector_select.append(option); + } + + if (typeof DOC_VERSIONS !== "undefined") { + var existing_versions = version_selector_select.children("option"); + var existing_versions_texts = existing_versions.map(function (i, x) { + return x.text; + }); + DOC_VERSIONS.forEach(function (each) { + var version_url = documenterBaseURL + "/../" + each + "/"; + var existing_id = $.inArray(each, existing_versions_texts); + // if not already in the version selector, add it as a new option, + // otherwise update the old option with the URL and enable it + if (existing_id == -1) { + var option = $( + "<option value='" + version_url + "'>" + each + "</option>", + ); + version_selector_select.append(option); + } else { + var option = existing_versions[existing_id]; + option.value = version_url; + option.disabled = false; + } + }); + } + + // only show the version selector if the selector has been populated + if (version_selector_select.children("option").length > 0) { + version_selector.toggleClass("visible"); + } +}); + +}) diff --git a/save/docs/build/assets/themes/catppuccin-frappe.css b/save/docs/build/assets/themes/catppuccin-frappe.css new file mode 100644 index 00000000..97bdba91 --- /dev/null +++ b/save/docs/build/assets/themes/catppuccin-frappe.css @@ -0,0 +1 @@ +๏ปฟhtml.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe .file-name,html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-frappe .pagination-previous:focus,html.theme--catppuccin-frappe .pagination-next:focus,html.theme--catppuccin-frappe .pagination-link:focus,html.theme--catppuccin-frappe .pagination-ellipsis:focus,html.theme--catppuccin-frappe .file-cta:focus,html.theme--catppuccin-frappe .file-name:focus,html.theme--catppuccin-frappe .select select:focus,html.theme--catppuccin-frappe .textarea:focus,html.theme--catppuccin-frappe .input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-frappe .button:focus,html.theme--catppuccin-frappe .is-focused.pagination-previous,html.theme--catppuccin-frappe .is-focused.pagination-next,html.theme--catppuccin-frappe .is-focused.pagination-link,html.theme--catppuccin-frappe .is-focused.pagination-ellipsis,html.theme--catppuccin-frappe .is-focused.file-cta,html.theme--catppuccin-frappe .is-focused.file-name,html.theme--catppuccin-frappe .select select.is-focused,html.theme--catppuccin-frappe .is-focused.textarea,html.theme--catppuccin-frappe .is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-focused.button,html.theme--catppuccin-frappe .pagination-previous:active,html.theme--catppuccin-frappe .pagination-next:active,html.theme--catppuccin-frappe .pagination-link:active,html.theme--catppuccin-frappe .pagination-ellipsis:active,html.theme--catppuccin-frappe .file-cta:active,html.theme--catppuccin-frappe .file-name:active,html.theme--catppuccin-frappe .select select:active,html.theme--catppuccin-frappe .textarea:active,html.theme--catppuccin-frappe .input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-frappe .button:active,html.theme--catppuccin-frappe .is-active.pagination-previous,html.theme--catppuccin-frappe .is-active.pagination-next,html.theme--catppuccin-frappe .is-active.pagination-link,html.theme--catppuccin-frappe .is-active.pagination-ellipsis,html.theme--catppuccin-frappe .is-active.file-cta,html.theme--catppuccin-frappe .is-active.file-name,html.theme--catppuccin-frappe .select select.is-active,html.theme--catppuccin-frappe .is-active.textarea,html.theme--catppuccin-frappe .is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .is-active.button{outline:none}html.theme--catppuccin-frappe .pagination-previous[disabled],html.theme--catppuccin-frappe .pagination-next[disabled],html.theme--catppuccin-frappe .pagination-link[disabled],html.theme--catppuccin-frappe .pagination-ellipsis[disabled],html.theme--catppuccin-frappe .file-cta[disabled],html.theme--catppuccin-frappe .file-name[disabled],html.theme--catppuccin-frappe .select select[disabled],html.theme--catppuccin-frappe .textarea[disabled],html.theme--catppuccin-frappe .input[disabled],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-frappe .button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-frappe .file-name,html.theme--catppuccin-frappe fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-frappe .select select,fieldset[disabled] html.theme--catppuccin-frappe .textarea,fieldset[disabled] html.theme--catppuccin-frappe .input,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe fieldset[disabled] .select select,html.theme--catppuccin-frappe .select fieldset[disabled] select,html.theme--catppuccin-frappe fieldset[disabled] .textarea,html.theme--catppuccin-frappe fieldset[disabled] .input,html.theme--catppuccin-frappe fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-frappe .button,html.theme--catppuccin-frappe fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-frappe .tabs,html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .breadcrumb,html.theme--catppuccin-frappe .file,html.theme--catppuccin-frappe .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-frappe .admonition:not(:last-child),html.theme--catppuccin-frappe .tabs:not(:last-child),html.theme--catppuccin-frappe .pagination:not(:last-child),html.theme--catppuccin-frappe .message:not(:last-child),html.theme--catppuccin-frappe .level:not(:last-child),html.theme--catppuccin-frappe .breadcrumb:not(:last-child),html.theme--catppuccin-frappe .block:not(:last-child),html.theme--catppuccin-frappe .title:not(:last-child),html.theme--catppuccin-frappe .subtitle:not(:last-child),html.theme--catppuccin-frappe .table-container:not(:last-child),html.theme--catppuccin-frappe .table:not(:last-child),html.theme--catppuccin-frappe .progress:not(:last-child),html.theme--catppuccin-frappe .notification:not(:last-child),html.theme--catppuccin-frappe .content:not(:last-child),html.theme--catppuccin-frappe .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .modal-close,html.theme--catppuccin-frappe .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-frappe .modal-close::before,html.theme--catppuccin-frappe .delete::before,html.theme--catppuccin-frappe .modal-close::after,html.theme--catppuccin-frappe .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-frappe .modal-close::before,html.theme--catppuccin-frappe .delete::before{height:2px;width:50%}html.theme--catppuccin-frappe .modal-close::after,html.theme--catppuccin-frappe .delete::after{height:50%;width:2px}html.theme--catppuccin-frappe .modal-close:hover,html.theme--catppuccin-frappe .delete:hover,html.theme--catppuccin-frappe .modal-close:focus,html.theme--catppuccin-frappe .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-frappe .modal-close:active,html.theme--catppuccin-frappe .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-frappe .is-small.modal-close,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-frappe .is-small.delete,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-frappe .is-medium.modal-close,html.theme--catppuccin-frappe .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-frappe .is-large.modal-close,html.theme--catppuccin-frappe .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-frappe .control.is-loading::after,html.theme--catppuccin-frappe .select.is-loading::after,html.theme--catppuccin-frappe .loader,html.theme--catppuccin-frappe .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #838ba7;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-frappe .hero-video,html.theme--catppuccin-frappe .modal-background,html.theme--catppuccin-frappe .modal,html.theme--catppuccin-frappe .image.is-square img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-frappe .image.is-square .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-frappe .image.is-1by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-frappe .image.is-1by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-frappe .image.is-5by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-frappe .image.is-5by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-frappe .image.is-4by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-frappe .image.is-4by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-frappe .image.is-3by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-frappe .image.is-5by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-frappe .image.is-5by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-frappe .image.is-16by9 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-frappe .image.is-16by9 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-frappe .image.is-2by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-frappe .image.is-2by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-frappe .image.is-3by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-frappe .image.is-3by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-frappe .image.is-4by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-frappe .image.is-4by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-frappe .image.is-3by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-frappe .image.is-3by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-frappe .image.is-2by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-frappe .image.is-2by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-frappe .image.is-3by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-frappe .image.is-9by16 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-frappe .image.is-9by16 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-frappe .image.is-1by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-frappe .image.is-1by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-frappe .image.is-1by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-frappe .image.is-1by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-frappe .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#414559 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#2b2e3c !important}.has-background-dark{background-color:#414559 !important}.has-text-primary{color:#8caaee !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#6089e7 !important}.has-background-primary{background-color:#8caaee !important}.has-text-primary-light{color:#edf2fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c1d1f6 !important}.has-background-primary-light{background-color:#edf2fc !important}.has-text-primary-dark{color:#153a8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#1c4cbb !important}.has-background-primary-dark{background-color:#153a8e !important}.has-text-link{color:#8caaee !important}a.has-text-link:hover,a.has-text-link:focus{color:#6089e7 !important}.has-background-link{background-color:#8caaee !important}.has-text-link-light{color:#edf2fc !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c1d1f6 !important}.has-background-link-light{background-color:#edf2fc !important}.has-text-link-dark{color:#153a8e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1c4cbb !important}.has-background-link-dark{background-color:#153a8e !important}.has-text-info{color:#81c8be !important}a.has-text-info:hover,a.has-text-info:focus{color:#5db9ac !important}.has-background-info{background-color:#81c8be !important}.has-text-info-light{color:#f1f9f8 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#cde9e5 !important}.has-background-info-light{background-color:#f1f9f8 !important}.has-text-info-dark{color:#2d675f !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#3c8a7f !important}.has-background-info-dark{background-color:#2d675f !important}.has-text-success{color:#a6d189 !important}a.has-text-success:hover,a.has-text-success:focus{color:#8ac364 !important}.has-background-success{background-color:#a6d189 !important}.has-text-success-light{color:#f4f9f0 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#d8ebcc !important}.has-background-success-light{background-color:#f4f9f0 !important}.has-text-success-dark{color:#446a29 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#5b8f38 !important}.has-background-success-dark{background-color:#446a29 !important}.has-text-warning{color:#e5c890 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#dbb467 !important}.has-background-warning{background-color:#e5c890 !important}.has-text-warning-light{color:#fbf7ee !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f1e2c5 !important}.has-background-warning-light{background-color:#fbf7ee !important}.has-text-warning-dark{color:#78591c !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#a17726 !important}.has-background-warning-dark{background-color:#78591c !important}.has-text-danger{color:#e78284 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#df575a !important}.has-background-danger{background-color:#e78284 !important}.has-text-danger-light{color:#fceeee !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f3c3c4 !important}.has-background-danger-light{background-color:#fceeee !important}.has-text-danger-dark{color:#9a1e20 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c52629 !important}.has-background-danger-dark{background-color:#9a1e20 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#414559 !important}.has-background-grey-darker{background-color:#414559 !important}.has-text-grey-dark{color:#51576d !important}.has-background-grey-dark{background-color:#51576d !important}.has-text-grey{color:#626880 !important}.has-background-grey{background-color:#626880 !important}.has-text-grey-light{color:#737994 !important}.has-background-grey-light{background-color:#737994 !important}.has-text-grey-lighter{color:#838ba7 !important}.has-background-grey-lighter{background-color:#838ba7 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-frappe html{background-color:#303446;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-frappe article,html.theme--catppuccin-frappe aside,html.theme--catppuccin-frappe figure,html.theme--catppuccin-frappe footer,html.theme--catppuccin-frappe header,html.theme--catppuccin-frappe hgroup,html.theme--catppuccin-frappe section{display:block}html.theme--catppuccin-frappe body,html.theme--catppuccin-frappe button,html.theme--catppuccin-frappe input,html.theme--catppuccin-frappe optgroup,html.theme--catppuccin-frappe select,html.theme--catppuccin-frappe textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-frappe code,html.theme--catppuccin-frappe pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-frappe body{color:#c6d0f5;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-frappe a{color:#8caaee;cursor:pointer;text-decoration:none}html.theme--catppuccin-frappe a strong{color:currentColor}html.theme--catppuccin-frappe a:hover{color:#99d1db}html.theme--catppuccin-frappe code{background-color:#292c3c;color:#c6d0f5;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-frappe hr{background-color:#292c3c;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-frappe img{height:auto;max-width:100%}html.theme--catppuccin-frappe input[type="checkbox"],html.theme--catppuccin-frappe input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-frappe small{font-size:.875em}html.theme--catppuccin-frappe span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-frappe strong{color:#b0bef1;font-weight:700}html.theme--catppuccin-frappe fieldset{border:none}html.theme--catppuccin-frappe pre{-webkit-overflow-scrolling:touch;background-color:#292c3c;color:#c6d0f5;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-frappe pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-frappe table td,html.theme--catppuccin-frappe table th{vertical-align:top}html.theme--catppuccin-frappe table td:not([align]),html.theme--catppuccin-frappe table th:not([align]){text-align:inherit}html.theme--catppuccin-frappe table th{color:#b0bef1}html.theme--catppuccin-frappe .box{background-color:#51576d;border-radius:8px;box-shadow:none;color:#c6d0f5;display:block;padding:1.25rem}html.theme--catppuccin-frappe a.box:hover,html.theme--catppuccin-frappe a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #8caaee}html.theme--catppuccin-frappe a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #8caaee}html.theme--catppuccin-frappe .button{background-color:#292c3c;border-color:#484d69;border-width:1px;color:#8caaee;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-frappe .button strong{color:inherit}html.theme--catppuccin-frappe .button .icon,html.theme--catppuccin-frappe .button .icon.is-small,html.theme--catppuccin-frappe .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-frappe .button .icon.is-medium,html.theme--catppuccin-frappe .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-frappe .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-frappe .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-frappe .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-frappe .button:hover,html.theme--catppuccin-frappe .button.is-hovered{border-color:#737994;color:#b0bef1}html.theme--catppuccin-frappe .button:focus,html.theme--catppuccin-frappe .button.is-focused{border-color:#737994;color:#769aeb}html.theme--catppuccin-frappe .button:focus:not(:active),html.theme--catppuccin-frappe .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button:active,html.theme--catppuccin-frappe .button.is-active{border-color:#51576d;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text{background-color:transparent;border-color:transparent;color:#c6d0f5;text-decoration:underline}html.theme--catppuccin-frappe .button.is-text:hover,html.theme--catppuccin-frappe .button.is-text.is-hovered,html.theme--catppuccin-frappe .button.is-text:focus,html.theme--catppuccin-frappe .button.is-text.is-focused{background-color:#292c3c;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text:active,html.theme--catppuccin-frappe .button.is-text.is-active{background-color:#1f212d;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-frappe .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#8caaee;text-decoration:none}html.theme--catppuccin-frappe .button.is-ghost:hover,html.theme--catppuccin-frappe .button.is-ghost.is-hovered{color:#8caaee;text-decoration:underline}html.theme--catppuccin-frappe .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:hover,html.theme--catppuccin-frappe .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:focus,html.theme--catppuccin-frappe .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:focus:not(:active),html.theme--catppuccin-frappe .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .button.is-white:active,html.theme--catppuccin-frappe .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-frappe .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted:hover,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-frappe .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-outlined:hover,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-white.is-outlined:focus,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:hover,html.theme--catppuccin-frappe .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:focus,html.theme--catppuccin-frappe .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:focus:not(:active),html.theme--catppuccin-frappe .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .button.is-black:active,html.theme--catppuccin-frappe .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-frappe .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted:hover,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-outlined:hover,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-black.is-outlined:focus,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:hover,html.theme--catppuccin-frappe .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:focus,html.theme--catppuccin-frappe .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:focus:not(:active),html.theme--catppuccin-frappe .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .button.is-light:active,html.theme--catppuccin-frappe .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-frappe .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted:hover,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-outlined:hover,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-light.is-outlined:focus,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-dark,html.theme--catppuccin-frappe .content kbd.button{background-color:#414559;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:hover,html.theme--catppuccin-frappe .content kbd.button:hover,html.theme--catppuccin-frappe .button.is-dark.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-hovered{background-color:#3c3f52;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:focus,html.theme--catppuccin-frappe .content kbd.button:focus,html.theme--catppuccin-frappe .button.is-dark.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:focus:not(:active),html.theme--catppuccin-frappe .content kbd.button:focus:not(:active),html.theme--catppuccin-frappe .button.is-dark.is-focused:not(:active),html.theme--catppuccin-frappe .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .button.is-dark:active,html.theme--catppuccin-frappe .content kbd.button:active,html.theme--catppuccin-frappe .button.is-dark.is-active,html.theme--catppuccin-frappe .content kbd.button.is-active{background-color:#363a4a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark[disabled],html.theme--catppuccin-frappe .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button{background-color:#414559;border-color:#414559;box-shadow:none}html.theme--catppuccin-frappe .button.is-dark.is-inverted,html.theme--catppuccin-frappe .content kbd.button.is-inverted{background-color:#fff;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted:hover,html.theme--catppuccin-frappe .content kbd.button.is-inverted:hover,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-dark.is-inverted[disabled],html.theme--catppuccin-frappe .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-loading::after,html.theme--catppuccin-frappe .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined,html.theme--catppuccin-frappe .content kbd.button.is-outlined{background-color:transparent;border-color:#414559;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-outlined:hover,html.theme--catppuccin-frappe .content kbd.button.is-outlined:hover,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-dark.is-outlined:focus,html.theme--catppuccin-frappe .content kbd.button.is-outlined:focus,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-focused{background-color:#414559;border-color:#414559;color:#fff}html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #414559 #414559 !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined[disabled],html.theme--catppuccin-frappe .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-outlined{background-color:transparent;border-color:#414559;box-shadow:none;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #414559 #414559 !important}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:hover,html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-hovered,html.theme--catppuccin-frappe details.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:focus,html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-focused,html.theme--catppuccin-frappe details.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:focus:not(:active),html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-frappe .button.is-primary.is-focused:not(:active),html.theme--catppuccin-frappe details.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button.is-primary:active,html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-frappe .button.is-primary.is-active,html.theme--catppuccin-frappe details.docstring>section>a.button.is-active.docs-sourcelink{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary[disabled],html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary,fieldset[disabled] html.theme--catppuccin-frappe details.docstring>section>a.button.docs-sourcelink{background-color:#8caaee;border-color:#8caaee;box-shadow:none}html.theme--catppuccin-frappe .button.is-primary.is-inverted,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted:hover,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-primary.is-inverted[disabled],html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-loading::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-outlined:hover,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-frappe .button.is-primary.is-outlined:focus,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined[disabled],html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8caaee;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-light,html.theme--catppuccin-frappe details.docstring>section>a.button.is-light.docs-sourcelink{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .button.is-primary.is-light:hover,html.theme--catppuccin-frappe details.docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-light.is-hovered,html.theme--catppuccin-frappe details.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e2eafb;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-primary.is-light:active,html.theme--catppuccin-frappe details.docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-frappe .button.is-primary.is-light.is-active,html.theme--catppuccin-frappe details.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d7e1f9;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-link{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:hover,html.theme--catppuccin-frappe .button.is-link.is-hovered{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:focus,html.theme--catppuccin-frappe .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:focus:not(:active),html.theme--catppuccin-frappe .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button.is-link:active,html.theme--catppuccin-frappe .button.is-link.is-active{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link{background-color:#8caaee;border-color:#8caaee;box-shadow:none}html.theme--catppuccin-frappe .button.is-link.is-inverted{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted:hover,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-link.is-outlined{background-color:transparent;border-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-outlined:hover,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-link.is-outlined:focus,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-focused{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-outlined{background-color:transparent;border-color:#8caaee;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-light{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .button.is-link.is-light:hover,html.theme--catppuccin-frappe .button.is-link.is-light.is-hovered{background-color:#e2eafb;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-link.is-light:active,html.theme--catppuccin-frappe .button.is-link.is-light.is-active{background-color:#d7e1f9;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-info{background-color:#81c8be;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:hover,html.theme--catppuccin-frappe .button.is-info.is-hovered{background-color:#78c4b9;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:focus,html.theme--catppuccin-frappe .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:focus:not(:active),html.theme--catppuccin-frappe .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .button.is-info:active,html.theme--catppuccin-frappe .button.is-info.is-active{background-color:#6fc0b5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info{background-color:#81c8be;border-color:#81c8be;box-shadow:none}html.theme--catppuccin-frappe .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted:hover,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-info.is-outlined{background-color:transparent;border-color:#81c8be;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-outlined:hover,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-info.is-outlined:focus,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-focused{background-color:#81c8be;border-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #81c8be #81c8be !important}html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-outlined{background-color:transparent;border-color:#81c8be;box-shadow:none;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #81c8be #81c8be !important}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-light{background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .button.is-info.is-light:hover,html.theme--catppuccin-frappe .button.is-info.is-light.is-hovered{background-color:#e8f5f3;border-color:transparent;color:#2d675f}html.theme--catppuccin-frappe .button.is-info.is-light:active,html.theme--catppuccin-frappe .button.is-info.is-light.is-active{background-color:#dff1ef;border-color:transparent;color:#2d675f}html.theme--catppuccin-frappe .button.is-success{background-color:#a6d189;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:hover,html.theme--catppuccin-frappe .button.is-success.is-hovered{background-color:#9fcd80;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:focus,html.theme--catppuccin-frappe .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:focus:not(:active),html.theme--catppuccin-frappe .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .button.is-success:active,html.theme--catppuccin-frappe .button.is-success.is-active{background-color:#98ca77;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success{background-color:#a6d189;border-color:#a6d189;box-shadow:none}html.theme--catppuccin-frappe .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted:hover,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-success.is-outlined{background-color:transparent;border-color:#a6d189;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-outlined:hover,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-success.is-outlined:focus,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-focused{background-color:#a6d189;border-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6d189 #a6d189 !important}html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-outlined{background-color:transparent;border-color:#a6d189;box-shadow:none;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6d189 #a6d189 !important}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-light{background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .button.is-success.is-light:hover,html.theme--catppuccin-frappe .button.is-success.is-light.is-hovered{background-color:#edf6e7;border-color:transparent;color:#446a29}html.theme--catppuccin-frappe .button.is-success.is-light:active,html.theme--catppuccin-frappe .button.is-success.is-light.is-active{background-color:#e6f2de;border-color:transparent;color:#446a29}html.theme--catppuccin-frappe .button.is-warning{background-color:#e5c890;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:hover,html.theme--catppuccin-frappe .button.is-warning.is-hovered{background-color:#e3c386;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:focus,html.theme--catppuccin-frappe .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:focus:not(:active),html.theme--catppuccin-frappe .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .button.is-warning:active,html.theme--catppuccin-frappe .button.is-warning.is-active{background-color:#e0be7b;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning{background-color:#e5c890;border-color:#e5c890;box-shadow:none}html.theme--catppuccin-frappe .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted:hover,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined{background-color:transparent;border-color:#e5c890;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-outlined:hover,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-warning.is-outlined:focus,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-focused{background-color:#e5c890;border-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #e5c890 #e5c890 !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-outlined{background-color:transparent;border-color:#e5c890;box-shadow:none;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #e5c890 #e5c890 !important}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-light{background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .button.is-warning.is-light:hover,html.theme--catppuccin-frappe .button.is-warning.is-light.is-hovered{background-color:#f9f2e4;border-color:transparent;color:#78591c}html.theme--catppuccin-frappe .button.is-warning.is-light:active,html.theme--catppuccin-frappe .button.is-warning.is-light.is-active{background-color:#f6edda;border-color:transparent;color:#78591c}html.theme--catppuccin-frappe .button.is-danger{background-color:#e78284;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:hover,html.theme--catppuccin-frappe .button.is-danger.is-hovered{background-color:#e57779;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:focus,html.theme--catppuccin-frappe .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:focus:not(:active),html.theme--catppuccin-frappe .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .button.is-danger:active,html.theme--catppuccin-frappe .button.is-danger.is-active{background-color:#e36d6f;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger{background-color:#e78284;border-color:#e78284;box-shadow:none}html.theme--catppuccin-frappe .button.is-danger.is-inverted{background-color:#fff;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted:hover,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined{background-color:transparent;border-color:#e78284;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-outlined:hover,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-danger.is-outlined:focus,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-focused{background-color:#e78284;border-color:#e78284;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #e78284 #e78284 !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-outlined{background-color:transparent;border-color:#e78284;box-shadow:none;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #e78284 #e78284 !important}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-light{background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .button.is-danger.is-light:hover,html.theme--catppuccin-frappe .button.is-danger.is-light.is-hovered{background-color:#fae3e4;border-color:transparent;color:#9a1e20}html.theme--catppuccin-frappe .button.is-danger.is-light:active,html.theme--catppuccin-frappe .button.is-danger.is-light.is-active{background-color:#f8d8d9;border-color:transparent;color:#9a1e20}html.theme--catppuccin-frappe .button.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-frappe .button.is-small:not(.is-rounded),html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-frappe .button.is-normal{font-size:1rem}html.theme--catppuccin-frappe .button.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .button.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button{background-color:#737994;border-color:#626880;box-shadow:none;opacity:.5}html.theme--catppuccin-frappe .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-frappe .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-frappe .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-frappe .button.is-static{background-color:#292c3c;border-color:#626880;color:#838ba7;box-shadow:none;pointer-events:none}html.theme--catppuccin-frappe .button.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-frappe .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-frappe .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-frappe .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-frappe .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-frappe .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-frappe .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-frappe .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-frappe .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-frappe .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-frappe .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-frappe .buttons.has-addons .button:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-frappe .buttons.has-addons .button:focus,html.theme--catppuccin-frappe .buttons.has-addons .button.is-focused,html.theme--catppuccin-frappe .buttons.has-addons .button:active,html.theme--catppuccin-frappe .buttons.has-addons .button.is-active,html.theme--catppuccin-frappe .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-frappe .buttons.has-addons .button:focus:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-frappe .buttons.has-addons .button:active:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-frappe .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .buttons.is-centered{justify-content:center}html.theme--catppuccin-frappe .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-frappe .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .button.is-responsive.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-frappe .button.is-responsive,html.theme--catppuccin-frappe .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-frappe .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-frappe .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .button.is-responsive.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-frappe .button.is-responsive,html.theme--catppuccin-frappe .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-frappe .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-frappe .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-frappe .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-frappe .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-frappe .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-frappe .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-frappe .content li+li{margin-top:0.25em}html.theme--catppuccin-frappe .content p:not(:last-child),html.theme--catppuccin-frappe .content dl:not(:last-child),html.theme--catppuccin-frappe .content ol:not(:last-child),html.theme--catppuccin-frappe .content ul:not(:last-child),html.theme--catppuccin-frappe .content blockquote:not(:last-child),html.theme--catppuccin-frappe .content pre:not(:last-child),html.theme--catppuccin-frappe .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-frappe .content h1,html.theme--catppuccin-frappe .content h2,html.theme--catppuccin-frappe .content h3,html.theme--catppuccin-frappe .content h4,html.theme--catppuccin-frappe .content h5,html.theme--catppuccin-frappe .content h6{color:#c6d0f5;font-weight:600;line-height:1.125}html.theme--catppuccin-frappe .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-frappe .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-frappe .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-frappe .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-frappe .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-frappe .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-frappe .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-frappe .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-frappe .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-frappe .content blockquote{background-color:#292c3c;border-left:5px solid #626880;padding:1.25em 1.5em}html.theme--catppuccin-frappe .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-frappe .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-frappe .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-frappe .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-frappe .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-frappe .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-frappe .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-frappe .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-frappe .content ul ul ul{list-style-type:square}html.theme--catppuccin-frappe .content dd{margin-left:2em}html.theme--catppuccin-frappe .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-frappe .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-frappe .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-frappe .content figure img{display:inline-block}html.theme--catppuccin-frappe .content figure figcaption{font-style:italic}html.theme--catppuccin-frappe .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-frappe .content sup,html.theme--catppuccin-frappe .content sub{font-size:75%}html.theme--catppuccin-frappe .content table{width:100%}html.theme--catppuccin-frappe .content table td,html.theme--catppuccin-frappe .content table th{border:1px solid #626880;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-frappe .content table th{color:#b0bef1}html.theme--catppuccin-frappe .content table th:not([align]){text-align:inherit}html.theme--catppuccin-frappe .content table thead td,html.theme--catppuccin-frappe .content table thead th{border-width:0 0 2px;color:#b0bef1}html.theme--catppuccin-frappe .content table tfoot td,html.theme--catppuccin-frappe .content table tfoot th{border-width:2px 0 0;color:#b0bef1}html.theme--catppuccin-frappe .content table tbody tr:last-child td,html.theme--catppuccin-frappe .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-frappe .content .tabs li+li{margin-top:0}html.theme--catppuccin-frappe .content.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-frappe .content.is-normal{font-size:1rem}html.theme--catppuccin-frappe .content.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .content.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-frappe .icon.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-frappe .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-frappe .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-frappe .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-frappe .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-frappe .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-frappe div.icon-text{display:flex}html.theme--catppuccin-frappe .image,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-frappe .image img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-frappe .image img.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-frappe .image.is-fullwidth,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-frappe .image.is-square img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-frappe .image.is-square .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-frappe .image.is-1by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-frappe .image.is-1by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-frappe .image.is-5by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-frappe .image.is-5by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-frappe .image.is-4by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-frappe .image.is-4by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-frappe .image.is-3by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-frappe .image.is-5by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-frappe .image.is-5by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-frappe .image.is-16by9 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-frappe .image.is-16by9 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-frappe .image.is-2by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-frappe .image.is-2by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-frappe .image.is-3by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-frappe .image.is-3by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-frappe .image.is-4by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-frappe .image.is-4by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-frappe .image.is-3by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-frappe .image.is-3by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-frappe .image.is-2by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-frappe .image.is-2by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-frappe .image.is-3by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-frappe .image.is-9by16 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-frappe .image.is-9by16 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-frappe .image.is-1by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-frappe .image.is-1by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-frappe .image.is-1by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-frappe .image.is-1by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-frappe .image.is-square,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-frappe .image.is-1by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-frappe .image.is-5by4,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-frappe .image.is-4by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-frappe .image.is-3by2,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-frappe .image.is-5by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-frappe .image.is-16by9,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-frappe .image.is-2by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-frappe .image.is-3by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-frappe .image.is-4by5,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-frappe .image.is-3by4,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-frappe .image.is-2by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-frappe .image.is-3by5,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-frappe .image.is-9by16,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-frappe .image.is-1by2,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-frappe .image.is-1by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-frappe .image.is-16x16,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-frappe .image.is-24x24,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-frappe .image.is-32x32,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-frappe .image.is-48x48,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-frappe .image.is-64x64,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-frappe .image.is-96x96,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-frappe .image.is-128x128,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-frappe .notification{background-color:#292c3c;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-frappe .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-frappe .notification strong{color:currentColor}html.theme--catppuccin-frappe .notification code,html.theme--catppuccin-frappe .notification pre{background:#fff}html.theme--catppuccin-frappe .notification pre code{background:transparent}html.theme--catppuccin-frappe .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-frappe .notification .title,html.theme--catppuccin-frappe .notification .subtitle,html.theme--catppuccin-frappe .notification .content{color:currentColor}html.theme--catppuccin-frappe .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-dark,html.theme--catppuccin-frappe .content kbd.notification{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .notification.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.notification.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .notification.is-primary.is-light,html.theme--catppuccin-frappe details.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .notification.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .notification.is-link.is-light{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .notification.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-info.is-light{background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .notification.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-success.is-light{background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .notification.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-warning.is-light{background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .notification.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .notification.is-danger.is-light{background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-frappe .progress::-webkit-progress-bar{background-color:#51576d}html.theme--catppuccin-frappe .progress::-webkit-progress-value{background-color:#838ba7}html.theme--catppuccin-frappe .progress::-moz-progress-bar{background-color:#838ba7}html.theme--catppuccin-frappe .progress::-ms-fill{background-color:#838ba7;border:none}html.theme--catppuccin-frappe .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-frappe .content kbd.progress::-webkit-progress-value{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-frappe .content kbd.progress::-moz-progress-bar{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark::-ms-fill,html.theme--catppuccin-frappe .content kbd.progress::-ms-fill{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark:indeterminate,html.theme--catppuccin-frappe .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #414559 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-frappe details.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-frappe details.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary::-ms-fill,html.theme--catppuccin-frappe details.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary:indeterminate,html.theme--catppuccin-frappe details.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #8caaee 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-link::-webkit-progress-value{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link::-moz-progress-bar{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link::-ms-fill{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link:indeterminate{background-image:linear-gradient(to right, #8caaee 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-info::-webkit-progress-value{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info::-moz-progress-bar{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info::-ms-fill{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info:indeterminate{background-image:linear-gradient(to right, #81c8be 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-success::-webkit-progress-value{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success::-moz-progress-bar{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success::-ms-fill{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6d189 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-warning::-webkit-progress-value{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning::-moz-progress-bar{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning::-ms-fill{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #e5c890 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-danger::-webkit-progress-value{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger::-moz-progress-bar{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger::-ms-fill{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #e78284 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#51576d;background-image:linear-gradient(to right, #c6d0f5 30%, #51576d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-frappe .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-frappe .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-frappe .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-frappe .progress.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-frappe .progress.is-medium{height:1.25rem}html.theme--catppuccin-frappe .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-frappe .table{background-color:#51576d;color:#c6d0f5}html.theme--catppuccin-frappe .table td,html.theme--catppuccin-frappe .table th{border:1px solid #626880;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-frappe .table td.is-white,html.theme--catppuccin-frappe .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .table td.is-black,html.theme--catppuccin-frappe .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .table td.is-light,html.theme--catppuccin-frappe .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-dark,html.theme--catppuccin-frappe .table th.is-dark{background-color:#414559;border-color:#414559;color:#fff}html.theme--catppuccin-frappe .table td.is-primary,html.theme--catppuccin-frappe .table th.is-primary{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-link,html.theme--catppuccin-frappe .table th.is-link{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-info,html.theme--catppuccin-frappe .table th.is-info{background-color:#81c8be;border-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-success,html.theme--catppuccin-frappe .table th.is-success{background-color:#a6d189;border-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-warning,html.theme--catppuccin-frappe .table th.is-warning{background-color:#e5c890;border-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-danger,html.theme--catppuccin-frappe .table th.is-danger{background-color:#e78284;border-color:#e78284;color:#fff}html.theme--catppuccin-frappe .table td.is-narrow,html.theme--catppuccin-frappe .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-frappe .table td.is-selected,html.theme--catppuccin-frappe .table th.is-selected{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-selected a,html.theme--catppuccin-frappe .table td.is-selected strong,html.theme--catppuccin-frappe .table th.is-selected a,html.theme--catppuccin-frappe .table th.is-selected strong{color:currentColor}html.theme--catppuccin-frappe .table td.is-vcentered,html.theme--catppuccin-frappe .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-frappe .table th{color:#b0bef1}html.theme--catppuccin-frappe .table th:not([align]){text-align:left}html.theme--catppuccin-frappe .table tr.is-selected{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table tr.is-selected a,html.theme--catppuccin-frappe .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-frappe .table tr.is-selected td,html.theme--catppuccin-frappe .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-frappe .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table thead td,html.theme--catppuccin-frappe .table thead th{border-width:0 0 2px;color:#b0bef1}html.theme--catppuccin-frappe .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table tfoot td,html.theme--catppuccin-frappe .table tfoot th{border-width:2px 0 0;color:#b0bef1}html.theme--catppuccin-frappe .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table tbody tr:last-child td,html.theme--catppuccin-frappe .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-frappe .table.is-bordered td,html.theme--catppuccin-frappe .table.is-bordered th{border-width:1px}html.theme--catppuccin-frappe .table.is-bordered tr:last-child td,html.theme--catppuccin-frappe .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-frappe .table.is-fullwidth{width:100%}html.theme--catppuccin-frappe .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#414559}html.theme--catppuccin-frappe .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#414559}html.theme--catppuccin-frappe .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#454a5f}html.theme--catppuccin-frappe .table.is-narrow td,html.theme--catppuccin-frappe .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-frappe .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#414559}html.theme--catppuccin-frappe .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-frappe .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .tags .tag,html.theme--catppuccin-frappe .tags .content kbd,html.theme--catppuccin-frappe .content .tags kbd,html.theme--catppuccin-frappe .tags details.docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-frappe .tags .tag:not(:last-child),html.theme--catppuccin-frappe .tags .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags kbd:not(:last-child),html.theme--catppuccin-frappe .tags details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-frappe .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-frappe .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-frappe .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .tags.are-medium details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-frappe .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .tags.are-large details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-frappe .tags.is-centered{justify-content:center}html.theme--catppuccin-frappe .tags.is-centered .tag,html.theme--catppuccin-frappe .tags.is-centered .content kbd,html.theme--catppuccin-frappe .content .tags.is-centered kbd,html.theme--catppuccin-frappe .tags.is-centered details.docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-frappe .tags.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .tags.is-right .tag:not(:first-child),html.theme--catppuccin-frappe .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-frappe .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-frappe .tags.is-right details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-frappe .tags.is-right .tag:not(:last-child),html.theme--catppuccin-frappe .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-frappe .tags.is-right details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-frappe .tags.has-addons .tag,html.theme--catppuccin-frappe .tags.has-addons .content kbd,html.theme--catppuccin-frappe .content .tags.has-addons kbd,html.theme--catppuccin-frappe .tags.has-addons details.docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-frappe .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-frappe .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-frappe .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-frappe .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-frappe .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-frappe .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-frappe .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-frappe .tag:not(body),html.theme--catppuccin-frappe .content kbd:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#292c3c;border-radius:.4em;color:#c6d0f5;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-frappe .tag:not(body) .delete,html.theme--catppuccin-frappe .content kbd:not(body) .delete,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-frappe .tag.is-white:not(body),html.theme--catppuccin-frappe .content kbd.is-white:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .tag.is-black:not(body),html.theme--catppuccin-frappe .content kbd.is-black:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .tag.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-dark:not(body),html.theme--catppuccin-frappe .content kbd:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-frappe .content details.docstring>section>kbd:not(body){background-color:#414559;color:#fff}html.theme--catppuccin-frappe .tag.is-primary:not(body),html.theme--catppuccin-frappe .content kbd.is-primary:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:not(body){background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .tag.is-primary.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .tag.is-link:not(body),html.theme--catppuccin-frappe .content kbd.is-link:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .tag.is-link.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-link.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .tag.is-info:not(body),html.theme--catppuccin-frappe .content kbd.is-info:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-info.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-info.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .tag.is-success:not(body),html.theme--catppuccin-frappe .content kbd.is-success:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-success.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-success.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .tag.is-warning:not(body),html.theme--catppuccin-frappe .content kbd.is-warning:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-warning.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .tag.is-danger:not(body),html.theme--catppuccin-frappe .content kbd.is-danger:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .tag.is-danger.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .tag.is-normal:not(body),html.theme--catppuccin-frappe .content kbd.is-normal:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-frappe .tag.is-medium:not(body),html.theme--catppuccin-frappe .content kbd.is-medium:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-frappe .tag.is-large:not(body),html.theme--catppuccin-frappe .content kbd.is-large:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-frappe .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-frappe .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-frappe .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-frappe .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-frappe .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-frappe .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-frappe .tag.is-delete:not(body),html.theme--catppuccin-frappe .content kbd.is-delete:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-frappe .tag.is-delete:not(body)::before,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::before,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-frappe .tag.is-delete:not(body)::after,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::after,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-frappe .tag.is-delete:not(body)::before,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::before,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-frappe .tag.is-delete:not(body)::after,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::after,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-frappe .tag.is-delete:not(body):hover,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):hover,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-frappe .tag.is-delete:not(body):focus,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):focus,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1f212d}html.theme--catppuccin-frappe .tag.is-delete:not(body):active,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):active,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#14161e}html.theme--catppuccin-frappe .tag.is-rounded:not(body),html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-frappe .content kbd.is-rounded:not(body),html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-frappe a.tag:hover,html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-frappe .title,html.theme--catppuccin-frappe .subtitle{word-break:break-word}html.theme--catppuccin-frappe .title em,html.theme--catppuccin-frappe .title span,html.theme--catppuccin-frappe .subtitle em,html.theme--catppuccin-frappe .subtitle span{font-weight:inherit}html.theme--catppuccin-frappe .title sub,html.theme--catppuccin-frappe .subtitle sub{font-size:.75em}html.theme--catppuccin-frappe .title sup,html.theme--catppuccin-frappe .subtitle sup{font-size:.75em}html.theme--catppuccin-frappe .title .tag,html.theme--catppuccin-frappe .title .content kbd,html.theme--catppuccin-frappe .content .title kbd,html.theme--catppuccin-frappe .title details.docstring>section>a.docs-sourcelink,html.theme--catppuccin-frappe .subtitle .tag,html.theme--catppuccin-frappe .subtitle .content kbd,html.theme--catppuccin-frappe .content .subtitle kbd,html.theme--catppuccin-frappe .subtitle details.docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-frappe .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-frappe .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-frappe .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-frappe .title.is-1{font-size:3rem}html.theme--catppuccin-frappe .title.is-2{font-size:2.5rem}html.theme--catppuccin-frappe .title.is-3{font-size:2rem}html.theme--catppuccin-frappe .title.is-4{font-size:1.5rem}html.theme--catppuccin-frappe .title.is-5{font-size:1.25rem}html.theme--catppuccin-frappe .title.is-6{font-size:1rem}html.theme--catppuccin-frappe .title.is-7{font-size:.75rem}html.theme--catppuccin-frappe .subtitle{color:#737994;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-frappe .subtitle strong{color:#737994;font-weight:600}html.theme--catppuccin-frappe .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-frappe .subtitle.is-1{font-size:3rem}html.theme--catppuccin-frappe .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-frappe .subtitle.is-3{font-size:2rem}html.theme--catppuccin-frappe .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-frappe .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-frappe .subtitle.is-6{font-size:1rem}html.theme--catppuccin-frappe .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-frappe .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-frappe .number{align-items:center;background-color:#292c3c;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{background-color:#303446;border-color:#626880;border-radius:.4em;color:#838ba7}html.theme--catppuccin-frappe .select select::-moz-placeholder,html.theme--catppuccin-frappe .textarea::-moz-placeholder,html.theme--catppuccin-frappe .input::-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select::-webkit-input-placeholder,html.theme--catppuccin-frappe .textarea::-webkit-input-placeholder,html.theme--catppuccin-frappe .input::-webkit-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:-moz-placeholder,html.theme--catppuccin-frappe .textarea:-moz-placeholder,html.theme--catppuccin-frappe .input:-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:-ms-input-placeholder,html.theme--catppuccin-frappe .textarea:-ms-input-placeholder,html.theme--catppuccin-frappe .input:-ms-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:hover,html.theme--catppuccin-frappe .textarea:hover,html.theme--catppuccin-frappe .input:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-frappe .select select.is-hovered,html.theme--catppuccin-frappe .is-hovered.textarea,html.theme--catppuccin-frappe .is-hovered.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#737994}html.theme--catppuccin-frappe .select select:focus,html.theme--catppuccin-frappe .textarea:focus,html.theme--catppuccin-frappe .input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-frappe .select select.is-focused,html.theme--catppuccin-frappe .is-focused.textarea,html.theme--catppuccin-frappe .is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .select select:active,html.theme--catppuccin-frappe .textarea:active,html.theme--catppuccin-frappe .input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-frappe .select select.is-active,html.theme--catppuccin-frappe .is-active.textarea,html.theme--catppuccin-frappe .is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#8caaee;box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select select[disabled],html.theme--catppuccin-frappe .textarea[disabled],html.theme--catppuccin-frappe .input[disabled],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-frappe .select select,fieldset[disabled] html.theme--catppuccin-frappe .textarea,fieldset[disabled] html.theme--catppuccin-frappe .input,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{background-color:#737994;border-color:#292c3c;box-shadow:none;color:#f1f4fd}html.theme--catppuccin-frappe .select select[disabled]::-moz-placeholder,html.theme--catppuccin-frappe .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-frappe .input[disabled]::-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]:-moz-placeholder,html.theme--catppuccin-frappe .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-frappe .input[disabled]:-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-frappe .textarea[readonly],html.theme--catppuccin-frappe .input[readonly],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-frappe .is-white.textarea,html.theme--catppuccin-frappe .is-white.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-frappe .is-white.textarea:focus,html.theme--catppuccin-frappe .is-white.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-frappe .is-white.is-focused.textarea,html.theme--catppuccin-frappe .is-white.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-white.textarea:active,html.theme--catppuccin-frappe .is-white.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-frappe .is-white.is-active.textarea,html.theme--catppuccin-frappe .is-white.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .is-black.textarea,html.theme--catppuccin-frappe .is-black.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-frappe .is-black.textarea:focus,html.theme--catppuccin-frappe .is-black.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-frappe .is-black.is-focused.textarea,html.theme--catppuccin-frappe .is-black.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-black.textarea:active,html.theme--catppuccin-frappe .is-black.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-frappe .is-black.is-active.textarea,html.theme--catppuccin-frappe .is-black.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .is-light.textarea,html.theme--catppuccin-frappe .is-light.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-frappe .is-light.textarea:focus,html.theme--catppuccin-frappe .is-light.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-frappe .is-light.is-focused.textarea,html.theme--catppuccin-frappe .is-light.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-light.textarea:active,html.theme--catppuccin-frappe .is-light.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-frappe .is-light.is-active.textarea,html.theme--catppuccin-frappe .is-light.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .is-dark.textarea,html.theme--catppuccin-frappe .content kbd.textarea,html.theme--catppuccin-frappe .is-dark.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-frappe .content kbd.input{border-color:#414559}html.theme--catppuccin-frappe .is-dark.textarea:focus,html.theme--catppuccin-frappe .content kbd.textarea:focus,html.theme--catppuccin-frappe .is-dark.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-frappe .content kbd.input:focus,html.theme--catppuccin-frappe .is-dark.is-focused.textarea,html.theme--catppuccin-frappe .content kbd.is-focused.textarea,html.theme--catppuccin-frappe .is-dark.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .content kbd.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-dark.textarea:active,html.theme--catppuccin-frappe .content kbd.textarea:active,html.theme--catppuccin-frappe .is-dark.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-frappe .content kbd.input:active,html.theme--catppuccin-frappe .is-dark.is-active.textarea,html.theme--catppuccin-frappe .content kbd.is-active.textarea,html.theme--catppuccin-frappe .is-dark.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .content kbd.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .is-primary.textarea,html.theme--catppuccin-frappe details.docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.input.docs-sourcelink{border-color:#8caaee}html.theme--catppuccin-frappe .is-primary.textarea:focus,html.theme--catppuccin-frappe details.docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-frappe .is-primary.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-frappe details.docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-frappe .is-primary.is-focused.textarea,html.theme--catppuccin-frappe details.docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe details.docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.textarea:active,html.theme--catppuccin-frappe details.docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-frappe .is-primary.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-frappe details.docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-frappe .is-primary.is-active.textarea,html.theme--catppuccin-frappe details.docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe details.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .is-link.textarea,html.theme--catppuccin-frappe .is-link.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#8caaee}html.theme--catppuccin-frappe .is-link.textarea:focus,html.theme--catppuccin-frappe .is-link.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-frappe .is-link.is-focused.textarea,html.theme--catppuccin-frappe .is-link.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-link.textarea:active,html.theme--catppuccin-frappe .is-link.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-frappe .is-link.is-active.textarea,html.theme--catppuccin-frappe .is-link.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .is-info.textarea,html.theme--catppuccin-frappe .is-info.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#81c8be}html.theme--catppuccin-frappe .is-info.textarea:focus,html.theme--catppuccin-frappe .is-info.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-frappe .is-info.is-focused.textarea,html.theme--catppuccin-frappe .is-info.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-info.textarea:active,html.theme--catppuccin-frappe .is-info.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-frappe .is-info.is-active.textarea,html.theme--catppuccin-frappe .is-info.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .is-success.textarea,html.theme--catppuccin-frappe .is-success.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6d189}html.theme--catppuccin-frappe .is-success.textarea:focus,html.theme--catppuccin-frappe .is-success.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-frappe .is-success.is-focused.textarea,html.theme--catppuccin-frappe .is-success.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-success.textarea:active,html.theme--catppuccin-frappe .is-success.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-frappe .is-success.is-active.textarea,html.theme--catppuccin-frappe .is-success.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .is-warning.textarea,html.theme--catppuccin-frappe .is-warning.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#e5c890}html.theme--catppuccin-frappe .is-warning.textarea:focus,html.theme--catppuccin-frappe .is-warning.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-frappe .is-warning.is-focused.textarea,html.theme--catppuccin-frappe .is-warning.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-warning.textarea:active,html.theme--catppuccin-frappe .is-warning.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-frappe .is-warning.is-active.textarea,html.theme--catppuccin-frappe .is-warning.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .is-danger.textarea,html.theme--catppuccin-frappe .is-danger.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#e78284}html.theme--catppuccin-frappe .is-danger.textarea:focus,html.theme--catppuccin-frappe .is-danger.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-frappe .is-danger.is-focused.textarea,html.theme--catppuccin-frappe .is-danger.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-danger.textarea:active,html.theme--catppuccin-frappe .is-danger.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-frappe .is-danger.is-active.textarea,html.theme--catppuccin-frappe .is-danger.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .is-small.textarea,html.theme--catppuccin-frappe .is-small.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-frappe .is-medium.textarea,html.theme--catppuccin-frappe .is-medium.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .is-large.textarea,html.theme--catppuccin-frappe .is-large.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .is-fullwidth.textarea,html.theme--catppuccin-frappe .is-fullwidth.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-frappe .is-inline.textarea,html.theme--catppuccin-frappe .is-inline.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-frappe .input.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-frappe .input.is-static,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-frappe .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-frappe .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-frappe .textarea[rows]{height:initial}html.theme--catppuccin-frappe .textarea.has-fixed-size{resize:none}html.theme--catppuccin-frappe .radio,html.theme--catppuccin-frappe .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-frappe .radio input,html.theme--catppuccin-frappe .checkbox input{cursor:pointer}html.theme--catppuccin-frappe .radio:hover,html.theme--catppuccin-frappe .checkbox:hover{color:#99d1db}html.theme--catppuccin-frappe .radio[disabled],html.theme--catppuccin-frappe .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-frappe .radio,fieldset[disabled] html.theme--catppuccin-frappe .checkbox,html.theme--catppuccin-frappe .radio input[disabled],html.theme--catppuccin-frappe .checkbox input[disabled]{color:#f1f4fd;cursor:not-allowed}html.theme--catppuccin-frappe .radio+.radio{margin-left:.5em}html.theme--catppuccin-frappe .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-frappe .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading)::after{border-color:#8caaee;right:1.125em;z-index:4}html.theme--catppuccin-frappe .select.is-rounded select,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-frappe .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-frappe .select select::-ms-expand{display:none}html.theme--catppuccin-frappe .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-frappe .select select:hover{border-color:#292c3c}html.theme--catppuccin-frappe .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-frappe .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-frappe .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#99d1db}html.theme--catppuccin-frappe .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-frappe .select.is-white select{border-color:#fff}html.theme--catppuccin-frappe .select.is-white select:hover,html.theme--catppuccin-frappe .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-frappe .select.is-white select:focus,html.theme--catppuccin-frappe .select.is-white select.is-focused,html.theme--catppuccin-frappe .select.is-white select:active,html.theme--catppuccin-frappe .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-frappe .select.is-black select:hover,html.theme--catppuccin-frappe .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-frappe .select.is-black select:focus,html.theme--catppuccin-frappe .select.is-black select.is-focused,html.theme--catppuccin-frappe .select.is-black select:active,html.theme--catppuccin-frappe .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-frappe .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-frappe .select.is-light select:hover,html.theme--catppuccin-frappe .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-frappe .select.is-light select:focus,html.theme--catppuccin-frappe .select.is-light select.is-focused,html.theme--catppuccin-frappe .select.is-light select:active,html.theme--catppuccin-frappe .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .select.is-dark:not(:hover)::after,html.theme--catppuccin-frappe .content kbd.select:not(:hover)::after{border-color:#414559}html.theme--catppuccin-frappe .select.is-dark select,html.theme--catppuccin-frappe .content kbd.select select{border-color:#414559}html.theme--catppuccin-frappe .select.is-dark select:hover,html.theme--catppuccin-frappe .content kbd.select select:hover,html.theme--catppuccin-frappe .select.is-dark select.is-hovered,html.theme--catppuccin-frappe .content kbd.select select.is-hovered{border-color:#363a4a}html.theme--catppuccin-frappe .select.is-dark select:focus,html.theme--catppuccin-frappe .content kbd.select select:focus,html.theme--catppuccin-frappe .select.is-dark select.is-focused,html.theme--catppuccin-frappe .content kbd.select select.is-focused,html.theme--catppuccin-frappe .select.is-dark select:active,html.theme--catppuccin-frappe .content kbd.select select:active,html.theme--catppuccin-frappe .select.is-dark select.is-active,html.theme--catppuccin-frappe .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .select.is-primary:not(:hover)::after,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-primary select,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-primary select:hover,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-frappe .select.is-primary select.is-hovered,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#769aeb}html.theme--catppuccin-frappe .select.is-primary select:focus,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-frappe .select.is-primary select.is-focused,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-frappe .select.is-primary select:active,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-frappe .select.is-primary select.is-active,html.theme--catppuccin-frappe details.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select.is-link:not(:hover)::after{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-link select{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-link select:hover,html.theme--catppuccin-frappe .select.is-link select.is-hovered{border-color:#769aeb}html.theme--catppuccin-frappe .select.is-link select:focus,html.theme--catppuccin-frappe .select.is-link select.is-focused,html.theme--catppuccin-frappe .select.is-link select:active,html.theme--catppuccin-frappe .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select.is-info:not(:hover)::after{border-color:#81c8be}html.theme--catppuccin-frappe .select.is-info select{border-color:#81c8be}html.theme--catppuccin-frappe .select.is-info select:hover,html.theme--catppuccin-frappe .select.is-info select.is-hovered{border-color:#6fc0b5}html.theme--catppuccin-frappe .select.is-info select:focus,html.theme--catppuccin-frappe .select.is-info select.is-focused,html.theme--catppuccin-frappe .select.is-info select:active,html.theme--catppuccin-frappe .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .select.is-success:not(:hover)::after{border-color:#a6d189}html.theme--catppuccin-frappe .select.is-success select{border-color:#a6d189}html.theme--catppuccin-frappe .select.is-success select:hover,html.theme--catppuccin-frappe .select.is-success select.is-hovered{border-color:#98ca77}html.theme--catppuccin-frappe .select.is-success select:focus,html.theme--catppuccin-frappe .select.is-success select.is-focused,html.theme--catppuccin-frappe .select.is-success select:active,html.theme--catppuccin-frappe .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .select.is-warning:not(:hover)::after{border-color:#e5c890}html.theme--catppuccin-frappe .select.is-warning select{border-color:#e5c890}html.theme--catppuccin-frappe .select.is-warning select:hover,html.theme--catppuccin-frappe .select.is-warning select.is-hovered{border-color:#e0be7b}html.theme--catppuccin-frappe .select.is-warning select:focus,html.theme--catppuccin-frappe .select.is-warning select.is-focused,html.theme--catppuccin-frappe .select.is-warning select:active,html.theme--catppuccin-frappe .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .select.is-danger:not(:hover)::after{border-color:#e78284}html.theme--catppuccin-frappe .select.is-danger select{border-color:#e78284}html.theme--catppuccin-frappe .select.is-danger select:hover,html.theme--catppuccin-frappe .select.is-danger select.is-hovered{border-color:#e36d6f}html.theme--catppuccin-frappe .select.is-danger select:focus,html.theme--catppuccin-frappe .select.is-danger select.is-focused,html.theme--catppuccin-frappe .select.is-danger select:active,html.theme--catppuccin-frappe .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .select.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-frappe .select.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .select.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .select.is-disabled::after{border-color:#f1f4fd !important;opacity:0.5}html.theme--catppuccin-frappe .select.is-fullwidth{width:100%}html.theme--catppuccin-frappe .select.is-fullwidth select{width:100%}html.theme--catppuccin-frappe .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-frappe .select.is-loading.is-small:after,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-frappe .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-frappe .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-frappe .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-frappe .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:hover .file-cta,html.theme--catppuccin-frappe .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:focus .file-cta,html.theme--catppuccin-frappe .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:active .file-cta,html.theme--catppuccin-frappe .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-black:hover .file-cta,html.theme--catppuccin-frappe .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-black:focus .file-cta,html.theme--catppuccin-frappe .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-black:active .file-cta,html.theme--catppuccin-frappe .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:hover .file-cta,html.theme--catppuccin-frappe .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:focus .file-cta,html.theme--catppuccin-frappe .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:active .file-cta,html.theme--catppuccin-frappe .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-dark .file-cta,html.theme--catppuccin-frappe .content kbd.file .file-cta{background-color:#414559;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-dark:hover .file-cta,html.theme--catppuccin-frappe .content kbd.file:hover .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-hovered .file-cta{background-color:#3c3f52;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-dark:focus .file-cta,html.theme--catppuccin-frappe .content kbd.file:focus .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-focused .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(65,69,89,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-dark:active .file-cta,html.theme--catppuccin-frappe .content kbd.file:active .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-active .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-active .file-cta{background-color:#363a4a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary:hover .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary:focus .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-focused .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(140,170,238,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-primary:active .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-active .file-cta,html.theme--catppuccin-frappe details.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link .file-cta{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link:hover .file-cta,html.theme--catppuccin-frappe .file.is-link.is-hovered .file-cta{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link:focus .file-cta,html.theme--catppuccin-frappe .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(140,170,238,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-link:active .file-cta,html.theme--catppuccin-frappe .file.is-link.is-active .file-cta{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-info .file-cta{background-color:#81c8be;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:hover .file-cta,html.theme--catppuccin-frappe .file.is-info.is-hovered .file-cta{background-color:#78c4b9;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:focus .file-cta,html.theme--catppuccin-frappe .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(129,200,190,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:active .file-cta,html.theme--catppuccin-frappe .file.is-info.is-active .file-cta{background-color:#6fc0b5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success .file-cta{background-color:#a6d189;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:hover .file-cta,html.theme--catppuccin-frappe .file.is-success.is-hovered .file-cta{background-color:#9fcd80;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:focus .file-cta,html.theme--catppuccin-frappe .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,209,137,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:active .file-cta,html.theme--catppuccin-frappe .file.is-success.is-active .file-cta{background-color:#98ca77;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning .file-cta{background-color:#e5c890;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:hover .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-hovered .file-cta{background-color:#e3c386;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:focus .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(229,200,144,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:active .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-active .file-cta{background-color:#e0be7b;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-danger .file-cta{background-color:#e78284;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-danger:hover .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-hovered .file-cta{background-color:#e57779;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-danger:focus .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(231,130,132,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-danger:active .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-active .file-cta{background-color:#e36d6f;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-frappe .file.is-normal{font-size:1rem}html.theme--catppuccin-frappe .file.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-frappe .file.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-frappe .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-frappe .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-frappe .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-frappe .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-frappe .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-frappe .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-frappe .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-frappe .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-frappe .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-frappe .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-frappe .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-frappe .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-frappe .file.is-centered{justify-content:center}html.theme--catppuccin-frappe .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-frappe .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-frappe .file.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-frappe .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-frappe .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-frappe .file-label:hover .file-cta{background-color:#3c3f52;color:#b0bef1}html.theme--catppuccin-frappe .file-label:hover .file-name{border-color:#5c6279}html.theme--catppuccin-frappe .file-label:active .file-cta{background-color:#363a4a;color:#b0bef1}html.theme--catppuccin-frappe .file-label:active .file-name{border-color:#575c72}html.theme--catppuccin-frappe .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe .file-name{border-color:#626880;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-frappe .file-cta{background-color:#414559;color:#c6d0f5}html.theme--catppuccin-frappe .file-name{border-color:#626880;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-frappe .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-frappe .file-icon .fa{font-size:14px}html.theme--catppuccin-frappe .label{color:#b0bef1;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-frappe .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-frappe .label.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-frappe .label.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .label.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-frappe .help.is-white{color:#fff}html.theme--catppuccin-frappe .help.is-black{color:#0a0a0a}html.theme--catppuccin-frappe .help.is-light{color:#f5f5f5}html.theme--catppuccin-frappe .help.is-dark,html.theme--catppuccin-frappe .content kbd.help{color:#414559}html.theme--catppuccin-frappe .help.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.help.docs-sourcelink{color:#8caaee}html.theme--catppuccin-frappe .help.is-link{color:#8caaee}html.theme--catppuccin-frappe .help.is-info{color:#81c8be}html.theme--catppuccin-frappe .help.is-success{color:#a6d189}html.theme--catppuccin-frappe .help.is-warning{color:#e5c890}html.theme--catppuccin-frappe .help.is-danger{color:#e78284}html.theme--catppuccin-frappe .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-frappe .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-frappe .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-frappe .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-frappe .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-frappe .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-frappe .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-frappe .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-frappe .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field.is-horizontal{display:flex}}html.theme--catppuccin-frappe .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-frappe .field-label.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-frappe .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-frappe .field-body .field{margin-bottom:0}html.theme--catppuccin-frappe .field-body>.field{flex-shrink:1}html.theme--catppuccin-frappe .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-frappe .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-frappe .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-frappe .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select:focus~.icon{color:#414559}html.theme--catppuccin-frappe .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-frappe .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-frappe .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-frappe .control.has-icons-left .icon,html.theme--catppuccin-frappe .control.has-icons-right .icon{color:#626880;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-frappe .control.has-icons-left .input,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-frappe .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-frappe .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-frappe .control.has-icons-right .input,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-frappe .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-frappe .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-frappe .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-frappe .control.is-loading.is-small:after,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-frappe .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-frappe .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-frappe .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-frappe .breadcrumb a{align-items:center;color:#8caaee;display:initial;justify-content:center;padding:0 .75em}html.theme--catppuccin-frappe .breadcrumb a:hover{color:#99d1db}html.theme--catppuccin-frappe .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-frappe .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-frappe .breadcrumb li.is-active a{color:#b0bef1;cursor:default;pointer-events:none}html.theme--catppuccin-frappe .breadcrumb li+li::before{color:#737994;content:"\0002f"}html.theme--catppuccin-frappe .breadcrumb ul,html.theme--catppuccin-frappe .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-frappe .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-frappe .breadcrumb.is-centered ol,html.theme--catppuccin-frappe .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-frappe .breadcrumb.is-right ol,html.theme--catppuccin-frappe .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-frappe .breadcrumb.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-frappe .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-frappe .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-frappe .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-frappe .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-frappe .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#c6d0f5;max-width:100%;position:relative}html.theme--catppuccin-frappe .card-footer:first-child,html.theme--catppuccin-frappe .card-content:first-child,html.theme--catppuccin-frappe .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-frappe .card-footer:last-child,html.theme--catppuccin-frappe .card-content:last-child,html.theme--catppuccin-frappe .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-frappe .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-frappe .card-header-title{align-items:center;color:#b0bef1;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-frappe .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-frappe .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-frappe .card-image{display:block;position:relative}html.theme--catppuccin-frappe .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-frappe .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-frappe .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-frappe .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-frappe .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-frappe .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-frappe .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-frappe .dropdown.is-active .dropdown-menu,html.theme--catppuccin-frappe .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-frappe .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-frappe .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-frappe .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-frappe .dropdown-content{background-color:#292c3c;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-frappe .dropdown-item{color:#c6d0f5;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-frappe a.dropdown-item,html.theme--catppuccin-frappe button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-frappe a.dropdown-item:hover,html.theme--catppuccin-frappe button.dropdown-item:hover{background-color:#292c3c;color:#0a0a0a}html.theme--catppuccin-frappe a.dropdown-item.is-active,html.theme--catppuccin-frappe button.dropdown-item.is-active{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-frappe .level{align-items:center;justify-content:space-between}html.theme--catppuccin-frappe .level code{border-radius:.4em}html.theme--catppuccin-frappe .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-frappe .level.is-mobile{display:flex}html.theme--catppuccin-frappe .level.is-mobile .level-left,html.theme--catppuccin-frappe .level.is-mobile .level-right{display:flex}html.theme--catppuccin-frappe .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-frappe .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-frappe .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level{display:flex}html.theme--catppuccin-frappe .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-frappe .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-frappe .level-item .title,html.theme--catppuccin-frappe .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-frappe .level-left,html.theme--catppuccin-frappe .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .level-left .level-item.is-flexible,html.theme--catppuccin-frappe .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-left .level-item:not(:last-child),html.theme--catppuccin-frappe .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-frappe .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-left{display:flex}}html.theme--catppuccin-frappe .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-right{display:flex}}html.theme--catppuccin-frappe .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-frappe .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-frappe .media .media{border-top:1px solid rgba(98,104,128,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-frappe .media .media .content:not(:last-child),html.theme--catppuccin-frappe .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-frappe .media .media .media{padding-top:.5rem}html.theme--catppuccin-frappe .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-frappe .media+.media{border-top:1px solid rgba(98,104,128,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-frappe .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-frappe .media-left,html.theme--catppuccin-frappe .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .media-left{margin-right:1rem}html.theme--catppuccin-frappe .media-right{margin-left:1rem}html.theme--catppuccin-frappe .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .media-content{overflow-x:auto}}html.theme--catppuccin-frappe .menu{font-size:1rem}html.theme--catppuccin-frappe .menu.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-frappe .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .menu.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .menu-list{line-height:1.25}html.theme--catppuccin-frappe .menu-list a{border-radius:3px;color:#c6d0f5;display:block;padding:0.5em 0.75em}html.theme--catppuccin-frappe .menu-list a:hover{background-color:#292c3c;color:#b0bef1}html.theme--catppuccin-frappe .menu-list a.is-active{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .menu-list li ul{border-left:1px solid #626880;margin:.75em;padding-left:.75em}html.theme--catppuccin-frappe .menu-label{color:#f1f4fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-frappe .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-frappe .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-frappe .message{background-color:#292c3c;border-radius:.4em;font-size:1rem}html.theme--catppuccin-frappe .message strong{color:currentColor}html.theme--catppuccin-frappe .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-frappe .message.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-frappe .message.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .message.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .message.is-white{background-color:#fff}html.theme--catppuccin-frappe .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-frappe .message.is-black{background-color:#fafafa}html.theme--catppuccin-frappe .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-frappe .message.is-light{background-color:#fafafa}html.theme--catppuccin-frappe .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-frappe .message.is-dark,html.theme--catppuccin-frappe .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-frappe .message.is-dark .message-header,html.theme--catppuccin-frappe .content kbd.message .message-header{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .message.is-dark .message-body,html.theme--catppuccin-frappe .content kbd.message .message-body{border-color:#414559}html.theme--catppuccin-frappe .message.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.message.docs-sourcelink{background-color:#edf2fc}html.theme--catppuccin-frappe .message.is-primary .message-header,html.theme--catppuccin-frappe details.docstring>section>a.message.docs-sourcelink .message-header{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .message.is-primary .message-body,html.theme--catppuccin-frappe details.docstring>section>a.message.docs-sourcelink .message-body{border-color:#8caaee;color:#153a8e}html.theme--catppuccin-frappe .message.is-link{background-color:#edf2fc}html.theme--catppuccin-frappe .message.is-link .message-header{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .message.is-link .message-body{border-color:#8caaee;color:#153a8e}html.theme--catppuccin-frappe .message.is-info{background-color:#f1f9f8}html.theme--catppuccin-frappe .message.is-info .message-header{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-info .message-body{border-color:#81c8be;color:#2d675f}html.theme--catppuccin-frappe .message.is-success{background-color:#f4f9f0}html.theme--catppuccin-frappe .message.is-success .message-header{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-success .message-body{border-color:#a6d189;color:#446a29}html.theme--catppuccin-frappe .message.is-warning{background-color:#fbf7ee}html.theme--catppuccin-frappe .message.is-warning .message-header{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-warning .message-body{border-color:#e5c890;color:#78591c}html.theme--catppuccin-frappe .message.is-danger{background-color:#fceeee}html.theme--catppuccin-frappe .message.is-danger .message-header{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .message.is-danger .message-body{border-color:#e78284;color:#9a1e20}html.theme--catppuccin-frappe .message-header{align-items:center;background-color:#c6d0f5;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-frappe .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-frappe .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .message-body{border-color:#626880;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#c6d0f5;padding:1.25em 1.5em}html.theme--catppuccin-frappe .message-body code,html.theme--catppuccin-frappe .message-body pre{background-color:#fff}html.theme--catppuccin-frappe .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-frappe .modal.is-active{display:flex}html.theme--catppuccin-frappe .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-frappe .modal-content,html.theme--catppuccin-frappe .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-frappe .modal-content,html.theme--catppuccin-frappe .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-frappe .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-frappe .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-frappe .modal-card-head,html.theme--catppuccin-frappe .modal-card-foot{align-items:center;background-color:#292c3c;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-frappe .modal-card-head{border-bottom:1px solid #626880;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-frappe .modal-card-title{color:#c6d0f5;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-frappe .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #626880}html.theme--catppuccin-frappe .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-frappe .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#303446;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-frappe .navbar{background-color:#8caaee;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-frappe .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-frappe .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-frappe .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-dark,html.theme--catppuccin-frappe .content kbd.navbar{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-burger,html.theme--catppuccin-frappe .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#414559;color:#fff}}html.theme--catppuccin-frappe .navbar.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-burger,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee;color:#fff}}html.theme--catppuccin-frappe .navbar.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee;color:#fff}}html.theme--catppuccin-frappe .navbar.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#81c8be;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6d189;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#e5c890;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#e78284;color:#fff}}html.theme--catppuccin-frappe .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-frappe .navbar.has-shadow{box-shadow:0 2px 0 0 #292c3c}html.theme--catppuccin-frappe .navbar.is-fixed-bottom,html.theme--catppuccin-frappe .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #292c3c}html.theme--catppuccin-frappe .navbar.is-fixed-top{top:0}html.theme--catppuccin-frappe html.has-navbar-fixed-top,html.theme--catppuccin-frappe body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-frappe .navbar-brand,html.theme--catppuccin-frappe .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-frappe .navbar-brand a.navbar-item:focus,html.theme--catppuccin-frappe .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-frappe .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-frappe .navbar-burger{color:#c6d0f5;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-frappe .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-frappe .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-frappe .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-frappe .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-frappe .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-frappe .navbar-menu{display:none}html.theme--catppuccin-frappe .navbar-item,html.theme--catppuccin-frappe .navbar-link{color:#c6d0f5;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-frappe .navbar-item .icon:only-child,html.theme--catppuccin-frappe .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-frappe a.navbar-item,html.theme--catppuccin-frappe .navbar-link{cursor:pointer}html.theme--catppuccin-frappe a.navbar-item:focus,html.theme--catppuccin-frappe a.navbar-item:focus-within,html.theme--catppuccin-frappe a.navbar-item:hover,html.theme--catppuccin-frappe a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar-link:focus,html.theme--catppuccin-frappe .navbar-link:focus-within,html.theme--catppuccin-frappe .navbar-link:hover,html.theme--catppuccin-frappe .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#8caaee}html.theme--catppuccin-frappe .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .navbar-item img{max-height:1.75rem}html.theme--catppuccin-frappe .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-frappe .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-frappe .navbar-item.is-tab:focus,html.theme--catppuccin-frappe .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#8caaee}html.theme--catppuccin-frappe .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#8caaee;border-bottom-style:solid;border-bottom-width:3px;color:#8caaee;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-frappe .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-frappe .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-frappe .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-frappe .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .navbar>.container{display:block}html.theme--catppuccin-frappe .navbar-brand .navbar-item,html.theme--catppuccin-frappe .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-frappe .navbar-link::after{display:none}html.theme--catppuccin-frappe .navbar-menu{background-color:#8caaee;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-frappe .navbar-menu.is-active{display:block}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch,html.theme--catppuccin-frappe .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-frappe .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-frappe .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-frappe html.has-navbar-fixed-top-touch,html.theme--catppuccin-frappe body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar,html.theme--catppuccin-frappe .navbar-menu,html.theme--catppuccin-frappe .navbar-start,html.theme--catppuccin-frappe .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-frappe .navbar{min-height:4rem}html.theme--catppuccin-frappe .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-frappe .navbar.is-spaced .navbar-start,html.theme--catppuccin-frappe .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-frappe .navbar.is-spaced a.navbar-item,html.theme--catppuccin-frappe .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#838ba7}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8caaee}html.theme--catppuccin-frappe .navbar-burger{display:none}html.theme--catppuccin-frappe .navbar-item,html.theme--catppuccin-frappe .navbar-link{align-items:center;display:flex}html.theme--catppuccin-frappe .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-frappe .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-frappe .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-frappe .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-frappe .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-frappe .navbar-dropdown{background-color:#8caaee;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-frappe .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#838ba7}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8caaee}.navbar.is-spaced html.theme--catppuccin-frappe .navbar-dropdown,html.theme--catppuccin-frappe .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-frappe .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-frappe .navbar-divider{display:block}html.theme--catppuccin-frappe .navbar>.container .navbar-brand,html.theme--catppuccin-frappe .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-frappe .navbar>.container .navbar-menu,html.theme--catppuccin-frappe .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-frappe .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-frappe html.has-navbar-fixed-top-desktop,html.theme--catppuccin-frappe body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-frappe html.has-spaced-navbar-fixed-top,html.theme--catppuccin-frappe body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-frappe html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-frappe body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-frappe a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar-link.is-active{color:#8caaee}html.theme--catppuccin-frappe a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-frappe .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-frappe .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-frappe .pagination.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-frappe .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .pagination.is-rounded .pagination-previous,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-frappe .pagination.is-rounded .pagination-next,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-frappe .pagination.is-rounded .pagination-link,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-frappe .pagination,html.theme--catppuccin-frappe .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link{border-color:#626880;color:#8caaee;min-width:2.5em}html.theme--catppuccin-frappe .pagination-previous:hover,html.theme--catppuccin-frappe .pagination-next:hover,html.theme--catppuccin-frappe .pagination-link:hover{border-color:#737994;color:#99d1db}html.theme--catppuccin-frappe .pagination-previous:focus,html.theme--catppuccin-frappe .pagination-next:focus,html.theme--catppuccin-frappe .pagination-link:focus{border-color:#737994}html.theme--catppuccin-frappe .pagination-previous:active,html.theme--catppuccin-frappe .pagination-next:active,html.theme--catppuccin-frappe .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-frappe .pagination-previous[disabled],html.theme--catppuccin-frappe .pagination-previous.is-disabled,html.theme--catppuccin-frappe .pagination-next[disabled],html.theme--catppuccin-frappe .pagination-next.is-disabled,html.theme--catppuccin-frappe .pagination-link[disabled],html.theme--catppuccin-frappe .pagination-link.is-disabled{background-color:#626880;border-color:#626880;box-shadow:none;color:#f1f4fd;opacity:0.5}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-frappe .pagination-link.is-current{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .pagination-ellipsis{color:#737994;pointer-events:none}html.theme--catppuccin-frappe .pagination-list{flex-wrap:wrap}html.theme--catppuccin-frappe .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .pagination{flex-wrap:wrap}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-frappe .pagination-previous{order:2}html.theme--catppuccin-frappe .pagination-next{order:3}html.theme--catppuccin-frappe .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-frappe .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-frappe .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-frappe .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-frappe .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-frappe .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-frappe .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-frappe .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-frappe .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-frappe .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-frappe .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-frappe .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-frappe .panel.is-dark .panel-heading,html.theme--catppuccin-frappe .content kbd.panel .panel-heading{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-frappe .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#414559}html.theme--catppuccin-frappe .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-frappe .content kbd.panel .panel-block.is-active .panel-icon{color:#414559}html.theme--catppuccin-frappe .panel.is-primary .panel-heading,html.theme--catppuccin-frappe details.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-frappe details.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#8caaee}html.theme--catppuccin-frappe .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-frappe details.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel.is-link .panel-heading{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .panel.is-link .panel-tabs a.is-active{border-bottom-color:#8caaee}html.theme--catppuccin-frappe .panel.is-link .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel.is-info .panel-heading{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-info .panel-tabs a.is-active{border-bottom-color:#81c8be}html.theme--catppuccin-frappe .panel.is-info .panel-block.is-active .panel-icon{color:#81c8be}html.theme--catppuccin-frappe .panel.is-success .panel-heading{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6d189}html.theme--catppuccin-frappe .panel.is-success .panel-block.is-active .panel-icon{color:#a6d189}html.theme--catppuccin-frappe .panel.is-warning .panel-heading{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#e5c890}html.theme--catppuccin-frappe .panel.is-warning .panel-block.is-active .panel-icon{color:#e5c890}html.theme--catppuccin-frappe .panel.is-danger .panel-heading{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#e78284}html.theme--catppuccin-frappe .panel.is-danger .panel-block.is-active .panel-icon{color:#e78284}html.theme--catppuccin-frappe .panel-tabs:not(:last-child),html.theme--catppuccin-frappe .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-frappe .panel-heading{background-color:#51576d;border-radius:8px 8px 0 0;color:#b0bef1;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-frappe .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-frappe .panel-tabs a{border-bottom:1px solid #626880;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-frappe .panel-tabs a.is-active{border-bottom-color:#51576d;color:#769aeb}html.theme--catppuccin-frappe .panel-list a{color:#c6d0f5}html.theme--catppuccin-frappe .panel-list a:hover{color:#8caaee}html.theme--catppuccin-frappe .panel-block{align-items:center;color:#b0bef1;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-frappe .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-frappe .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-frappe .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-frappe .panel-block.is-active{border-left-color:#8caaee;color:#769aeb}html.theme--catppuccin-frappe .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-frappe a.panel-block,html.theme--catppuccin-frappe label.panel-block{cursor:pointer}html.theme--catppuccin-frappe a.panel-block:hover,html.theme--catppuccin-frappe label.panel-block:hover{background-color:#292c3c}html.theme--catppuccin-frappe .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f1f4fd;margin-right:.75em}html.theme--catppuccin-frappe .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-frappe .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-frappe .tabs a{align-items:center;border-bottom-color:#626880;border-bottom-style:solid;border-bottom-width:1px;color:#c6d0f5;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-frappe .tabs a:hover{border-bottom-color:#b0bef1;color:#b0bef1}html.theme--catppuccin-frappe .tabs li{display:block}html.theme--catppuccin-frappe .tabs li.is-active a{border-bottom-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .tabs ul{align-items:center;border-bottom-color:#626880;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-frappe .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-frappe .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-frappe .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-frappe .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-frappe .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-frappe .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-frappe .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-frappe .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-frappe .tabs.is-boxed a:hover{background-color:#292c3c;border-bottom-color:#626880}html.theme--catppuccin-frappe .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#626880;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-frappe .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .tabs.is-toggle a{border-color:#626880;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-frappe .tabs.is-toggle a:hover{background-color:#292c3c;border-color:#737994;z-index:2}html.theme--catppuccin-frappe .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-frappe .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-frappe .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-frappe .tabs.is-toggle li.is-active a{background-color:#8caaee;border-color:#8caaee;color:#fff;z-index:1}html.theme--catppuccin-frappe .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-frappe .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-frappe .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-frappe .tabs.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-frappe .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .column.is-narrow,html.theme--catppuccin-frappe .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full,html.theme--catppuccin-frappe .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters,html.theme--catppuccin-frappe .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds,html.theme--catppuccin-frappe .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half,html.theme--catppuccin-frappe .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third,html.theme--catppuccin-frappe .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter,html.theme--catppuccin-frappe .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth,html.theme--catppuccin-frappe .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths,html.theme--catppuccin-frappe .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths,html.theme--catppuccin-frappe .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths,html.theme--catppuccin-frappe .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters,html.theme--catppuccin-frappe .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds,html.theme--catppuccin-frappe .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half,html.theme--catppuccin-frappe .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third,html.theme--catppuccin-frappe .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter,html.theme--catppuccin-frappe .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth,html.theme--catppuccin-frappe .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths,html.theme--catppuccin-frappe .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths,html.theme--catppuccin-frappe .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths,html.theme--catppuccin-frappe .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-frappe .column.is-0,html.theme--catppuccin-frappe .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0,html.theme--catppuccin-frappe .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-frappe .column.is-1,html.theme--catppuccin-frappe .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1,html.theme--catppuccin-frappe .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2,html.theme--catppuccin-frappe .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2,html.theme--catppuccin-frappe .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3,html.theme--catppuccin-frappe .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3,html.theme--catppuccin-frappe .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-frappe .column.is-4,html.theme--catppuccin-frappe .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4,html.theme--catppuccin-frappe .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5,html.theme--catppuccin-frappe .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5,html.theme--catppuccin-frappe .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6,html.theme--catppuccin-frappe .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6,html.theme--catppuccin-frappe .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-frappe .column.is-7,html.theme--catppuccin-frappe .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7,html.theme--catppuccin-frappe .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8,html.theme--catppuccin-frappe .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8,html.theme--catppuccin-frappe .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9,html.theme--catppuccin-frappe .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9,html.theme--catppuccin-frappe .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-frappe .column.is-10,html.theme--catppuccin-frappe .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10,html.theme--catppuccin-frappe .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11,html.theme--catppuccin-frappe .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11,html.theme--catppuccin-frappe .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12,html.theme--catppuccin-frappe .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12,html.theme--catppuccin-frappe .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-frappe .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-frappe .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-frappe .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-frappe .columns.is-centered{justify-content:center}html.theme--catppuccin-frappe .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-frappe .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-frappe .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-frappe .columns.is-mobile{display:flex}html.theme--catppuccin-frappe .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-frappe .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-desktop{display:flex}}html.theme--catppuccin-frappe .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-frappe .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-frappe .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-frappe .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-frappe .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-frappe .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-frappe .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-frappe .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-frappe .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-frappe .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-frappe .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-frappe .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-frappe .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-frappe .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-frappe .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-frappe .tile.is-child{margin:0 !important}html.theme--catppuccin-frappe .tile.is-parent{padding:.75rem}html.theme--catppuccin-frappe .tile.is-vertical{flex-direction:column}html.theme--catppuccin-frappe .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .tile:not(.is-child){display:flex}html.theme--catppuccin-frappe .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .tile.is-3{flex:none;width:25%}html.theme--catppuccin-frappe .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .tile.is-6{flex:none;width:50%}html.theme--catppuccin-frappe .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .tile.is-9{flex:none;width:75%}html.theme--catppuccin-frappe .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-frappe .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-frappe .hero .navbar{background:none}html.theme--catppuccin-frappe .hero .tabs ul{border-bottom:none}html.theme--catppuccin-frappe .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-white strong{color:inherit}html.theme--catppuccin-frappe .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-frappe .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-frappe .hero.is-white .navbar-item,html.theme--catppuccin-frappe .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-frappe .hero.is-white a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-white .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-frappe .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-frappe .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-black strong{color:inherit}html.theme--catppuccin-frappe .hero.is-black .title{color:#fff}html.theme--catppuccin-frappe .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-frappe .hero.is-black .navbar-item,html.theme--catppuccin-frappe .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-black a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-black .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-frappe .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-light strong{color:inherit}html.theme--catppuccin-frappe .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-frappe .hero.is-light .navbar-item,html.theme--catppuccin-frappe .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-light .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-frappe .hero.is-dark,html.theme--catppuccin-frappe .content kbd.hero{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-dark strong,html.theme--catppuccin-frappe .content kbd.hero strong{color:inherit}html.theme--catppuccin-frappe .hero.is-dark .title,html.theme--catppuccin-frappe .content kbd.hero .title{color:#fff}html.theme--catppuccin-frappe .hero.is-dark .subtitle,html.theme--catppuccin-frappe .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-frappe .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-dark .subtitle strong,html.theme--catppuccin-frappe .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-dark .navbar-menu,html.theme--catppuccin-frappe .content kbd.hero .navbar-menu{background-color:#414559}}html.theme--catppuccin-frappe .hero.is-dark .navbar-item,html.theme--catppuccin-frappe .content kbd.hero .navbar-item,html.theme--catppuccin-frappe .hero.is-dark .navbar-link,html.theme--catppuccin-frappe .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-dark .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.hero .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.hero .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .hero.is-dark .tabs a,html.theme--catppuccin-frappe .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-dark .tabs a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs li.is-active a{color:#414559 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#414559}html.theme--catppuccin-frappe .hero.is-dark.is-bold,html.theme--catppuccin-frappe .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #262f41 0%, #414559 71%, #47476c 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-frappe .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #262f41 0%, #414559 71%, #47476c 100%)}}html.theme--catppuccin-frappe .hero.is-primary,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-primary strong,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-frappe .hero.is-primary .title,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-frappe .hero.is-primary .subtitle,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-primary .subtitle strong,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-primary .navbar-menu,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#8caaee}}html.theme--catppuccin-frappe .hero.is-primary .navbar-item,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-frappe .hero.is-primary .navbar-link,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-primary .navbar-link:hover,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .hero.is-primary .tabs a,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-primary .tabs a:hover,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#8caaee !important;opacity:1}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .hero.is-primary.is-bold,html.theme--catppuccin-frappe details.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-frappe details.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}}html.theme--catppuccin-frappe .hero.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-link strong{color:inherit}html.theme--catppuccin-frappe .hero.is-link .title{color:#fff}html.theme--catppuccin-frappe .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-link .navbar-menu{background-color:#8caaee}}html.theme--catppuccin-frappe .hero.is-link .navbar-item,html.theme--catppuccin-frappe .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-link a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-link .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-link .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-link .tabs li.is-active a{color:#8caaee !important;opacity:1}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .hero.is-link.is-bold{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}}html.theme--catppuccin-frappe .hero.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-info strong{color:inherit}html.theme--catppuccin-frappe .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-info .navbar-menu{background-color:#81c8be}}html.theme--catppuccin-frappe .hero.is-info .navbar-item,html.theme--catppuccin-frappe .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-info .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-info .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-info .tabs li.is-active a{color:#81c8be !important;opacity:1}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .hero.is-info.is-bold{background-image:linear-gradient(141deg, #52c4a1 0%, #81c8be 71%, #8fd2d4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #52c4a1 0%, #81c8be 71%, #8fd2d4 100%)}}html.theme--catppuccin-frappe .hero.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-success strong{color:inherit}html.theme--catppuccin-frappe .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-success .navbar-menu{background-color:#a6d189}}html.theme--catppuccin-frappe .hero.is-success .navbar-item,html.theme--catppuccin-frappe .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-success .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-success .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-success .tabs li.is-active a{color:#a6d189 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .hero.is-success.is-bold{background-image:linear-gradient(141deg, #9ccd5a 0%, #a6d189 71%, #a8dc98 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #9ccd5a 0%, #a6d189 71%, #a8dc98 100%)}}html.theme--catppuccin-frappe .hero.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-warning strong{color:inherit}html.theme--catppuccin-frappe .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-warning .navbar-menu{background-color:#e5c890}}html.theme--catppuccin-frappe .hero.is-warning .navbar-item,html.theme--catppuccin-frappe .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-warning .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-warning .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-warning .tabs li.is-active a{color:#e5c890 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #e5a05d 0%, #e5c890 71%, #ede0a2 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e5a05d 0%, #e5c890 71%, #ede0a2 100%)}}html.theme--catppuccin-frappe .hero.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-danger strong{color:inherit}html.theme--catppuccin-frappe .hero.is-danger .title{color:#fff}html.theme--catppuccin-frappe .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-danger .navbar-menu{background-color:#e78284}}html.theme--catppuccin-frappe .hero.is-danger .navbar-item,html.theme--catppuccin-frappe .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-danger .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-danger .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-danger .tabs li.is-active a{color:#e78284 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#e78284}html.theme--catppuccin-frappe .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #e94d6a 0%, #e78284 71%, #eea294 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e94d6a 0%, #e78284 71%, #eea294 100%)}}html.theme--catppuccin-frappe .hero.is-small .hero-body,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-frappe .hero.is-halfheight .hero-body,html.theme--catppuccin-frappe .hero.is-fullheight .hero-body,html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-frappe .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-frappe .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-frappe .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-frappe .hero-video{overflow:hidden}html.theme--catppuccin-frappe .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-frappe .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero-video{display:none}}html.theme--catppuccin-frappe .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero-buttons .button{display:flex}html.theme--catppuccin-frappe .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-frappe .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-frappe .hero-head,html.theme--catppuccin-frappe .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero-body{padding:3rem 3rem}}html.theme--catppuccin-frappe .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .section{padding:3rem 3rem}html.theme--catppuccin-frappe .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-frappe .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-frappe .footer{background-color:#292c3c;padding:3rem 1.5rem 6rem}html.theme--catppuccin-frappe h1 .docs-heading-anchor,html.theme--catppuccin-frappe h1 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h1 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h2 .docs-heading-anchor,html.theme--catppuccin-frappe h2 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h2 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h3 .docs-heading-anchor,html.theme--catppuccin-frappe h3 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h3 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h4 .docs-heading-anchor,html.theme--catppuccin-frappe h4 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h4 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h5 .docs-heading-anchor,html.theme--catppuccin-frappe h5 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h5 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h6 .docs-heading-anchor,html.theme--catppuccin-frappe h6 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h6 .docs-heading-anchor:visited{color:#c6d0f5}html.theme--catppuccin-frappe h1 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h2 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h3 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h4 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h5 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-frappe h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-frappe h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-frappe .docs-light-only{display:none !important}html.theme--catppuccin-frappe pre{position:relative;overflow:hidden}html.theme--catppuccin-frappe pre code,html.theme--catppuccin-frappe pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-frappe pre code:first-of-type,html.theme--catppuccin-frappe pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-frappe pre code:last-of-type,html.theme--catppuccin-frappe pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-frappe pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#c6d0f5;cursor:pointer;text-align:center}html.theme--catppuccin-frappe pre .copy-button:focus,html.theme--catppuccin-frappe pre .copy-button:hover{opacity:1;background:rgba(198,208,245,0.1);color:#8caaee}html.theme--catppuccin-frappe pre .copy-button.success{color:#a6d189;opacity:1}html.theme--catppuccin-frappe pre .copy-button.error{color:#e78284;opacity:1}html.theme--catppuccin-frappe pre:hover .copy-button{opacity:1}html.theme--catppuccin-frappe .link-icon:hover{color:#8caaee}html.theme--catppuccin-frappe .admonition{background-color:#292c3c;border-style:solid;border-width:2px;border-color:#b5bfe2;border-radius:4px;font-size:1rem}html.theme--catppuccin-frappe .admonition strong{color:currentColor}html.theme--catppuccin-frappe .admonition.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-frappe .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .admonition.is-default{background-color:#292c3c;border-color:#b5bfe2}html.theme--catppuccin-frappe .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#b5bfe2}html.theme--catppuccin-frappe .admonition.is-default>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-info{background-color:#292c3c;border-color:#81c8be}html.theme--catppuccin-frappe .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#81c8be}html.theme--catppuccin-frappe .admonition.is-info>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-success{background-color:#292c3c;border-color:#a6d189}html.theme--catppuccin-frappe .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6d189}html.theme--catppuccin-frappe .admonition.is-success>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-warning{background-color:#292c3c;border-color:#e5c890}html.theme--catppuccin-frappe .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#e5c890}html.theme--catppuccin-frappe .admonition.is-warning>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-danger{background-color:#292c3c;border-color:#e78284}html.theme--catppuccin-frappe .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#e78284}html.theme--catppuccin-frappe .admonition.is-danger>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-compat{background-color:#292c3c;border-color:#99d1db}html.theme--catppuccin-frappe .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#99d1db}html.theme--catppuccin-frappe .admonition.is-compat>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-todo{background-color:#292c3c;border-color:#ca9ee6}html.theme--catppuccin-frappe .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#ca9ee6}html.theme--catppuccin-frappe .admonition.is-todo>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition-header{color:#b5bfe2;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-frappe .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-frappe .admonition-header .admonition-anchor{opacity:0;margin-left:0.5em;font-size:0.75em;color:inherit;text-decoration:none;transition:opacity 0.2s ease-in-out}html.theme--catppuccin-frappe .admonition-header .admonition-anchor:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-frappe .admonition-header .admonition-anchor:hover{opacity:1 !important;text-decoration:none}html.theme--catppuccin-frappe .admonition-header:hover .admonition-anchor{opacity:0.8}html.theme--catppuccin-frappe details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-frappe details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-frappe details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-frappe .admonition-body{color:#c6d0f5;padding:0.5rem .75rem}html.theme--catppuccin-frappe .admonition-body pre{background-color:#292c3c}html.theme--catppuccin-frappe .admonition-body code{background-color:#292c3c}html.theme--catppuccin-frappe details.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #626880;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-frappe details.docstring>summary{list-style-type:none;align-items:stretch;padding:0.5rem .75rem;background-color:#292c3c;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #626880;overflow:auto}html.theme--catppuccin-frappe details.docstring>summary code{background-color:transparent}html.theme--catppuccin-frappe details.docstring>summary .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-frappe details.docstring>summary .docstring-binding{margin-right:0.3em}html.theme--catppuccin-frappe details.docstring>summary .docstring-category{margin-left:0.3em}html.theme--catppuccin-frappe details.docstring>summary::before{content:'\f054';font-family:"Font Awesome 6 Free";font-weight:900;min-width:1.1rem;color:#2E63BD;display:inline-block}html.theme--catppuccin-frappe details.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #626880}html.theme--catppuccin-frappe details.docstring>section:last-child{border-bottom:none}html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-frappe details.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-frappe details.docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-frappe details.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-frappe details.docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-frappe details.docstring[open]>summary::before{content:"\f078"}html.theme--catppuccin-frappe .documenter-example-output{background-color:#303446}html.theme--catppuccin-frappe .warning-overlay-base,html.theme--catppuccin-frappe .dev-warning-overlay,html.theme--catppuccin-frappe .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-frappe .warning-overlay-base .outdated-warning-closer,html.theme--catppuccin-frappe .dev-warning-overlay .outdated-warning-closer,html.theme--catppuccin-frappe .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-frappe .warning-overlay-base a,html.theme--catppuccin-frappe .dev-warning-overlay a,html.theme--catppuccin-frappe .outdated-warning-overlay a{color:#8caaee}html.theme--catppuccin-frappe .warning-overlay-base a:hover,html.theme--catppuccin-frappe .dev-warning-overlay a:hover,html.theme--catppuccin-frappe .outdated-warning-overlay a:hover{color:#99d1db}html.theme--catppuccin-frappe .outdated-warning-overlay{background-color:#292c3c;color:#c6d0f5;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-frappe .dev-warning-overlay{background-color:#292c3c;color:#c6d0f5;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-frappe .footnote-reference{position:relative;display:inline-block}html.theme--catppuccin-frappe .footnote-preview{display:none;position:absolute;z-index:1000;max-width:300px;width:max-content;background-color:#303446;border:1px solid #99d1db;padding:10px;border-radius:5px;top:calc(100% + 10px);left:50%;transform:translateX(-50%);box-sizing:border-box;--arrow-left: 50%}html.theme--catppuccin-frappe .footnote-preview::before{content:"";position:absolute;top:-10px;left:var(--arrow-left);transform:translateX(-50%);border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #99d1db}html.theme--catppuccin-frappe .content pre{border:2px solid #626880;border-radius:4px}html.theme--catppuccin-frappe .content code{font-weight:inherit}html.theme--catppuccin-frappe .content a code{color:#8caaee}html.theme--catppuccin-frappe .content a:hover code{color:#99d1db}html.theme--catppuccin-frappe .content h1 code,html.theme--catppuccin-frappe .content h2 code,html.theme--catppuccin-frappe .content h3 code,html.theme--catppuccin-frappe .content h4 code,html.theme--catppuccin-frappe .content h5 code,html.theme--catppuccin-frappe .content h6 code{color:#c6d0f5}html.theme--catppuccin-frappe .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-frappe .content blockquote>ul:first-child,html.theme--catppuccin-frappe .content blockquote>ol:first-child,html.theme--catppuccin-frappe .content .admonition-body>ul:first-child,html.theme--catppuccin-frappe .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-frappe pre,html.theme--catppuccin-frappe code{font-variant-ligatures:no-contextual}html.theme--catppuccin-frappe .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-frappe .breadcrumb a.is-disabled,html.theme--catppuccin-frappe .breadcrumb a.is-disabled:hover{color:#b0bef1}html.theme--catppuccin-frappe .hljs{background:initial !important}html.theme--catppuccin-frappe .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-frappe .katex-display,html.theme--catppuccin-frappe mjx-container,html.theme--catppuccin-frappe .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-frappe html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-frappe li.no-marker{list-style:none}html.theme--catppuccin-frappe #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-frappe #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main{width:100%}html.theme--catppuccin-frappe #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-frappe #documenter .docs-main>header,html.theme--catppuccin-frappe #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar{background-color:#303446;border-bottom:1px solid #626880;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow:hidden}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-frappe #documenter .docs-main section.footnotes{border-top:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li details.docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-frappe .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #626880;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-frappe #documenter .docs-sidebar{display:flex;flex-direction:column;color:#c6d0f5;background-color:#292c3c;border-right:1px solid #626880;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-frappe #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name a:hover{color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #626880;display:none;padding:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #626880;padding-bottom:1.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#c6d0f5;background:#292c3c}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#c6d0f5;background-color:#313548}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #626880;border-bottom:1px solid #626880;background-color:#232634}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#232634;color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#313548;color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"โšฌ";margin-right:0.4em}html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-frappe #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3a3e54}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4a506c}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3a3e54}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4a506c}}html.theme--catppuccin-frappe kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-frappe .search-min-width-50{min-width:50%}html.theme--catppuccin-frappe .search-min-height-100{min-height:100%}html.theme--catppuccin-frappe .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-frappe .search-result-link{border-radius:0.7em;transition:all 300ms;border:1px solid transparent}html.theme--catppuccin-frappe .search-result-link:hover,html.theme--catppuccin-frappe .search-result-link:focus{background-color:rgba(0,128,128,0.1);outline:none;border-color:#81c8be}html.theme--catppuccin-frappe .search-result-link .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-frappe .property-search-result-badge,html.theme--catppuccin-frappe .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-frappe .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:hover .search-filter,html.theme--catppuccin-frappe .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-frappe .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-frappe .search-filter:hover,html.theme--catppuccin-frappe .search-filter:focus{color:#333}html.theme--catppuccin-frappe .search-filter-selected{color:#414559;background-color:#babbf1}html.theme--catppuccin-frappe .search-filter-selected:hover,html.theme--catppuccin-frappe .search-filter-selected:focus{color:#414559}html.theme--catppuccin-frappe .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-frappe .search-divider{border-bottom:1px solid #626880}html.theme--catppuccin-frappe .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-frappe .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-frappe .w-100{width:100%}html.theme--catppuccin-frappe .gap-2{gap:0.5rem}html.theme--catppuccin-frappe .gap-4{gap:1rem}html.theme--catppuccin-frappe .gap-8{gap:2rem}html.theme--catppuccin-frappe{background-color:#303446;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-frappe a{transition:all 200ms ease}html.theme--catppuccin-frappe .label{color:#c6d0f5}html.theme--catppuccin-frappe .button,html.theme--catppuccin-frappe .control.has-icons-left .icon,html.theme--catppuccin-frappe .control.has-icons-right .icon,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .select,html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea{height:2.5em;color:#c6d0f5}html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#c6d0f5}html.theme--catppuccin-frappe .select:after,html.theme--catppuccin-frappe .select select{border-width:1px}html.theme--catppuccin-frappe .menu-list a{transition:all 300ms ease}html.theme--catppuccin-frappe .modal-card-foot,html.theme--catppuccin-frappe .modal-card-head{border-color:#626880}html.theme--catppuccin-frappe .navbar{border-radius:.4em}html.theme--catppuccin-frappe .navbar.is-transparent{background:none}html.theme--catppuccin-frappe .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .navbar .navbar-menu{background-color:#8caaee;border-radius:0 0 .4em .4em}}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){color:#414559}html.theme--catppuccin-frappe .tag.is-link:not(body),html.theme--catppuccin-frappe details.docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-frappe .content kbd.is-link:not(body){color:#414559}html.theme--catppuccin-frappe .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-frappe .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-frappe .ansi span.sgr3{font-style:italic}html.theme--catppuccin-frappe .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-frappe .ansi span.sgr7{color:#303446;background-color:#c6d0f5}html.theme--catppuccin-frappe .ansi span.sgr8{color:transparent}html.theme--catppuccin-frappe .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-frappe .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-frappe .ansi span.sgr30{color:#51576d}html.theme--catppuccin-frappe .ansi span.sgr31{color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr32{color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr33{color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr34{color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr35{color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr36{color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr37{color:#b5bfe2}html.theme--catppuccin-frappe .ansi span.sgr40{background-color:#51576d}html.theme--catppuccin-frappe .ansi span.sgr41{background-color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr42{background-color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr43{background-color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr44{background-color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr45{background-color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr46{background-color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr47{background-color:#b5bfe2}html.theme--catppuccin-frappe .ansi span.sgr90{color:#626880}html.theme--catppuccin-frappe .ansi span.sgr91{color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr92{color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr93{color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr94{color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr95{color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr96{color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr97{color:#a5adce}html.theme--catppuccin-frappe .ansi span.sgr100{background-color:#626880}html.theme--catppuccin-frappe .ansi span.sgr101{background-color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr102{background-color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr103{background-color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr104{background-color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr105{background-color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr106{background-color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr107{background-color:#a5adce}html.theme--catppuccin-frappe code.language-julia-repl>span.hljs-meta{color:#a6d189;font-weight:bolder}html.theme--catppuccin-frappe code .hljs{color:#c6d0f5;background:#303446}html.theme--catppuccin-frappe code .hljs-keyword{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-built_in{color:#e78284}html.theme--catppuccin-frappe code .hljs-type{color:#e5c890}html.theme--catppuccin-frappe code .hljs-literal{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-number{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-operator{color:#81c8be}html.theme--catppuccin-frappe code .hljs-punctuation{color:#b5bfe2}html.theme--catppuccin-frappe code .hljs-property{color:#81c8be}html.theme--catppuccin-frappe code .hljs-regexp{color:#f4b8e4}html.theme--catppuccin-frappe code .hljs-string{color:#a6d189}html.theme--catppuccin-frappe code .hljs-char.escape_{color:#a6d189}html.theme--catppuccin-frappe code .hljs-subst{color:#a5adce}html.theme--catppuccin-frappe code .hljs-symbol{color:#eebebe}html.theme--catppuccin-frappe code .hljs-variable{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-variable.language_{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-variable.constant_{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-title{color:#8caaee}html.theme--catppuccin-frappe code .hljs-title.class_{color:#e5c890}html.theme--catppuccin-frappe code .hljs-title.function_{color:#8caaee}html.theme--catppuccin-frappe code .hljs-params{color:#c6d0f5}html.theme--catppuccin-frappe code .hljs-comment{color:#626880}html.theme--catppuccin-frappe code .hljs-doctag{color:#e78284}html.theme--catppuccin-frappe code .hljs-meta{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-section{color:#8caaee}html.theme--catppuccin-frappe code .hljs-tag{color:#a5adce}html.theme--catppuccin-frappe code .hljs-name{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-attr{color:#8caaee}html.theme--catppuccin-frappe code .hljs-attribute{color:#a6d189}html.theme--catppuccin-frappe code .hljs-bullet{color:#81c8be}html.theme--catppuccin-frappe code .hljs-code{color:#a6d189}html.theme--catppuccin-frappe code .hljs-emphasis{color:#e78284;font-style:italic}html.theme--catppuccin-frappe code .hljs-strong{color:#e78284;font-weight:bold}html.theme--catppuccin-frappe code .hljs-formula{color:#81c8be}html.theme--catppuccin-frappe code .hljs-link{color:#85c1dc;font-style:italic}html.theme--catppuccin-frappe code .hljs-quote{color:#a6d189;font-style:italic}html.theme--catppuccin-frappe code .hljs-selector-tag{color:#e5c890}html.theme--catppuccin-frappe code .hljs-selector-id{color:#8caaee}html.theme--catppuccin-frappe code .hljs-selector-class{color:#81c8be}html.theme--catppuccin-frappe code .hljs-selector-attr{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-selector-pseudo{color:#81c8be}html.theme--catppuccin-frappe code .hljs-template-tag{color:#eebebe}html.theme--catppuccin-frappe code .hljs-template-variable{color:#eebebe}html.theme--catppuccin-frappe code .hljs-addition{color:#a6d189;background:rgba(166,227,161,0.15)}html.theme--catppuccin-frappe code .hljs-deletion{color:#e78284;background:rgba(243,139,168,0.15)}html.theme--catppuccin-frappe .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover,html.theme--catppuccin-frappe .search-result-link:focus{background-color:#414559}html.theme--catppuccin-frappe .search-result-link .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:hover .search-filter,html.theme--catppuccin-frappe .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:focus .search-filter{color:#414559 !important;background-color:#babbf1 !important}html.theme--catppuccin-frappe .search-result-title{color:#c6d0f5}html.theme--catppuccin-frappe .search-result-highlight{background-color:#e78284;color:#292c3c}html.theme--catppuccin-frappe .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-frappe .w-100{width:100%}html.theme--catppuccin-frappe .gap-2{gap:0.5rem}html.theme--catppuccin-frappe .gap-4{gap:1rem} diff --git a/save/docs/build/assets/themes/catppuccin-latte.css b/save/docs/build/assets/themes/catppuccin-latte.css new file mode 100644 index 00000000..7f1e6c62 --- /dev/null +++ b/save/docs/build/assets/themes/catppuccin-latte.css @@ -0,0 +1 @@ +๏ปฟhtml.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte .file-name,html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-latte .pagination-previous:focus,html.theme--catppuccin-latte .pagination-next:focus,html.theme--catppuccin-latte .pagination-link:focus,html.theme--catppuccin-latte .pagination-ellipsis:focus,html.theme--catppuccin-latte .file-cta:focus,html.theme--catppuccin-latte .file-name:focus,html.theme--catppuccin-latte .select select:focus,html.theme--catppuccin-latte .textarea:focus,html.theme--catppuccin-latte .input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-latte .button:focus,html.theme--catppuccin-latte .is-focused.pagination-previous,html.theme--catppuccin-latte .is-focused.pagination-next,html.theme--catppuccin-latte .is-focused.pagination-link,html.theme--catppuccin-latte .is-focused.pagination-ellipsis,html.theme--catppuccin-latte .is-focused.file-cta,html.theme--catppuccin-latte .is-focused.file-name,html.theme--catppuccin-latte .select select.is-focused,html.theme--catppuccin-latte .is-focused.textarea,html.theme--catppuccin-latte .is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-focused.button,html.theme--catppuccin-latte .pagination-previous:active,html.theme--catppuccin-latte .pagination-next:active,html.theme--catppuccin-latte .pagination-link:active,html.theme--catppuccin-latte .pagination-ellipsis:active,html.theme--catppuccin-latte .file-cta:active,html.theme--catppuccin-latte .file-name:active,html.theme--catppuccin-latte .select select:active,html.theme--catppuccin-latte .textarea:active,html.theme--catppuccin-latte .input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-latte .button:active,html.theme--catppuccin-latte .is-active.pagination-previous,html.theme--catppuccin-latte .is-active.pagination-next,html.theme--catppuccin-latte .is-active.pagination-link,html.theme--catppuccin-latte .is-active.pagination-ellipsis,html.theme--catppuccin-latte .is-active.file-cta,html.theme--catppuccin-latte .is-active.file-name,html.theme--catppuccin-latte .select select.is-active,html.theme--catppuccin-latte .is-active.textarea,html.theme--catppuccin-latte .is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .is-active.button{outline:none}html.theme--catppuccin-latte .pagination-previous[disabled],html.theme--catppuccin-latte .pagination-next[disabled],html.theme--catppuccin-latte .pagination-link[disabled],html.theme--catppuccin-latte .pagination-ellipsis[disabled],html.theme--catppuccin-latte .file-cta[disabled],html.theme--catppuccin-latte .file-name[disabled],html.theme--catppuccin-latte .select select[disabled],html.theme--catppuccin-latte .textarea[disabled],html.theme--catppuccin-latte .input[disabled],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-latte .button[disabled],fieldset[disabled] html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-latte .file-name,html.theme--catppuccin-latte fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-latte .select select,fieldset[disabled] html.theme--catppuccin-latte .textarea,fieldset[disabled] html.theme--catppuccin-latte .input,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte fieldset[disabled] .select select,html.theme--catppuccin-latte .select fieldset[disabled] select,html.theme--catppuccin-latte fieldset[disabled] .textarea,html.theme--catppuccin-latte fieldset[disabled] .input,html.theme--catppuccin-latte fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-latte .button,html.theme--catppuccin-latte fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-latte .tabs,html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .breadcrumb,html.theme--catppuccin-latte .file,html.theme--catppuccin-latte .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-latte .admonition:not(:last-child),html.theme--catppuccin-latte .tabs:not(:last-child),html.theme--catppuccin-latte .pagination:not(:last-child),html.theme--catppuccin-latte .message:not(:last-child),html.theme--catppuccin-latte .level:not(:last-child),html.theme--catppuccin-latte .breadcrumb:not(:last-child),html.theme--catppuccin-latte .block:not(:last-child),html.theme--catppuccin-latte .title:not(:last-child),html.theme--catppuccin-latte .subtitle:not(:last-child),html.theme--catppuccin-latte .table-container:not(:last-child),html.theme--catppuccin-latte .table:not(:last-child),html.theme--catppuccin-latte .progress:not(:last-child),html.theme--catppuccin-latte .notification:not(:last-child),html.theme--catppuccin-latte .content:not(:last-child),html.theme--catppuccin-latte .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .modal-close,html.theme--catppuccin-latte .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-latte .modal-close::before,html.theme--catppuccin-latte .delete::before,html.theme--catppuccin-latte .modal-close::after,html.theme--catppuccin-latte .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-latte .modal-close::before,html.theme--catppuccin-latte .delete::before{height:2px;width:50%}html.theme--catppuccin-latte .modal-close::after,html.theme--catppuccin-latte .delete::after{height:50%;width:2px}html.theme--catppuccin-latte .modal-close:hover,html.theme--catppuccin-latte .delete:hover,html.theme--catppuccin-latte .modal-close:focus,html.theme--catppuccin-latte .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-latte .modal-close:active,html.theme--catppuccin-latte .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-latte .is-small.modal-close,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-latte .is-small.delete,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-latte .is-medium.modal-close,html.theme--catppuccin-latte .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-latte .is-large.modal-close,html.theme--catppuccin-latte .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-latte .control.is-loading::after,html.theme--catppuccin-latte .select.is-loading::after,html.theme--catppuccin-latte .loader,html.theme--catppuccin-latte .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #8c8fa1;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-latte .hero-video,html.theme--catppuccin-latte .modal-background,html.theme--catppuccin-latte .modal,html.theme--catppuccin-latte .image.is-square img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-latte .image.is-square .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-latte .image.is-1by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-latte .image.is-1by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-latte .image.is-5by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-latte .image.is-5by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-latte .image.is-4by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-latte .image.is-4by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-latte .image.is-3by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-latte .image.is-5by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-latte .image.is-5by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-latte .image.is-16by9 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-latte .image.is-16by9 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-latte .image.is-2by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-latte .image.is-2by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-latte .image.is-3by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-latte .image.is-3by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-latte .image.is-4by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-latte .image.is-4by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-latte .image.is-3by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-latte .image.is-3by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-latte .image.is-2by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-latte .image.is-2by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-latte .image.is-3by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-latte .image.is-9by16 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-latte .image.is-9by16 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-latte .image.is-1by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-latte .image.is-1by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-latte .image.is-1by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-latte .image.is-1by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-latte .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#ccd0da !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#aeb5c5 !important}.has-background-dark{background-color:#ccd0da !important}.has-text-primary{color:#1e66f5 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#0a4ed6 !important}.has-background-primary{background-color:#1e66f5 !important}.has-text-primary-light{color:#ebf2fe !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bbd1fc !important}.has-background-primary-light{background-color:#ebf2fe !important}.has-text-primary-dark{color:#0a52e1 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#286df5 !important}.has-background-primary-dark{background-color:#0a52e1 !important}.has-text-link{color:#1e66f5 !important}a.has-text-link:hover,a.has-text-link:focus{color:#0a4ed6 !important}.has-background-link{background-color:#1e66f5 !important}.has-text-link-light{color:#ebf2fe !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bbd1fc !important}.has-background-link-light{background-color:#ebf2fe !important}.has-text-link-dark{color:#0a52e1 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#286df5 !important}.has-background-link-dark{background-color:#0a52e1 !important}.has-text-info{color:#179299 !important}a.has-text-info:hover,a.has-text-info:focus{color:#10686d !important}.has-background-info{background-color:#179299 !important}.has-text-info-light{color:#edfcfc !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c1f3f6 !important}.has-background-info-light{background-color:#edfcfc !important}.has-text-info-dark{color:#1cb2ba !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#2ad5df !important}.has-background-info-dark{background-color:#1cb2ba !important}.has-text-success{color:#40a02b !important}a.has-text-success:hover,a.has-text-success:focus{color:#307820 !important}.has-background-success{background-color:#40a02b !important}.has-text-success-light{color:#f1fbef !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#cef0c7 !important}.has-background-success-light{background-color:#f1fbef !important}.has-text-success-dark{color:#40a12b !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#50c936 !important}.has-background-success-dark{background-color:#40a12b !important}.has-text-warning{color:#df8e1d !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#b27117 !important}.has-background-warning{background-color:#df8e1d !important}.has-text-warning-light{color:#fdf6ed !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f7e0c0 !important}.has-background-warning-light{background-color:#fdf6ed !important}.has-text-warning-dark{color:#9e6515 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#cb811a !important}.has-background-warning-dark{background-color:#9e6515 !important}.has-text-danger{color:#d20f39 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a20c2c !important}.has-background-danger{background-color:#d20f39 !important}.has-text-danger-light{color:#feecf0 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fabcca !important}.has-background-danger-light{background-color:#feecf0 !important}.has-text-danger-dark{color:#e9113f !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#f13c63 !important}.has-background-danger-dark{background-color:#e9113f !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#ccd0da !important}.has-background-grey-darker{background-color:#ccd0da !important}.has-text-grey-dark{color:#bcc0cc !important}.has-background-grey-dark{background-color:#bcc0cc !important}.has-text-grey{color:#acb0be !important}.has-background-grey{background-color:#acb0be !important}.has-text-grey-light{color:#9ca0b0 !important}.has-background-grey-light{background-color:#9ca0b0 !important}.has-text-grey-lighter{color:#8c8fa1 !important}.has-background-grey-lighter{background-color:#8c8fa1 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-latte html{background-color:#eff1f5;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-latte article,html.theme--catppuccin-latte aside,html.theme--catppuccin-latte figure,html.theme--catppuccin-latte footer,html.theme--catppuccin-latte header,html.theme--catppuccin-latte hgroup,html.theme--catppuccin-latte section{display:block}html.theme--catppuccin-latte body,html.theme--catppuccin-latte button,html.theme--catppuccin-latte input,html.theme--catppuccin-latte optgroup,html.theme--catppuccin-latte select,html.theme--catppuccin-latte textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-latte code,html.theme--catppuccin-latte pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-latte body{color:#4c4f69;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-latte a{color:#1e66f5;cursor:pointer;text-decoration:none}html.theme--catppuccin-latte a strong{color:currentColor}html.theme--catppuccin-latte a:hover{color:#04a5e5}html.theme--catppuccin-latte code{background-color:#e6e9ef;color:#4c4f69;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-latte hr{background-color:#e6e9ef;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-latte img{height:auto;max-width:100%}html.theme--catppuccin-latte input[type="checkbox"],html.theme--catppuccin-latte input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-latte small{font-size:.875em}html.theme--catppuccin-latte span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-latte strong{color:#41445a;font-weight:700}html.theme--catppuccin-latte fieldset{border:none}html.theme--catppuccin-latte pre{-webkit-overflow-scrolling:touch;background-color:#e6e9ef;color:#4c4f69;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-latte pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-latte table td,html.theme--catppuccin-latte table th{vertical-align:top}html.theme--catppuccin-latte table td:not([align]),html.theme--catppuccin-latte table th:not([align]){text-align:inherit}html.theme--catppuccin-latte table th{color:#41445a}html.theme--catppuccin-latte .box{background-color:#bcc0cc;border-radius:8px;box-shadow:none;color:#4c4f69;display:block;padding:1.25rem}html.theme--catppuccin-latte a.box:hover,html.theme--catppuccin-latte a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1e66f5}html.theme--catppuccin-latte a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1e66f5}html.theme--catppuccin-latte .button{background-color:#e6e9ef;border-color:#fff;border-width:1px;color:#1e66f5;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-latte .button strong{color:inherit}html.theme--catppuccin-latte .button .icon,html.theme--catppuccin-latte .button .icon.is-small,html.theme--catppuccin-latte .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-latte .button .icon.is-medium,html.theme--catppuccin-latte .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-latte .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-latte .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-latte .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-latte .button:hover,html.theme--catppuccin-latte .button.is-hovered{border-color:#9ca0b0;color:#41445a}html.theme--catppuccin-latte .button:focus,html.theme--catppuccin-latte .button.is-focused{border-color:#9ca0b0;color:#0b57ef}html.theme--catppuccin-latte .button:focus:not(:active),html.theme--catppuccin-latte .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button:active,html.theme--catppuccin-latte .button.is-active{border-color:#bcc0cc;color:#41445a}html.theme--catppuccin-latte .button.is-text{background-color:transparent;border-color:transparent;color:#4c4f69;text-decoration:underline}html.theme--catppuccin-latte .button.is-text:hover,html.theme--catppuccin-latte .button.is-text.is-hovered,html.theme--catppuccin-latte .button.is-text:focus,html.theme--catppuccin-latte .button.is-text.is-focused{background-color:#e6e9ef;color:#41445a}html.theme--catppuccin-latte .button.is-text:active,html.theme--catppuccin-latte .button.is-text.is-active{background-color:#d6dbe5;color:#41445a}html.theme--catppuccin-latte .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-latte .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1e66f5;text-decoration:none}html.theme--catppuccin-latte .button.is-ghost:hover,html.theme--catppuccin-latte .button.is-ghost.is-hovered{color:#1e66f5;text-decoration:underline}html.theme--catppuccin-latte .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:hover,html.theme--catppuccin-latte .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:focus,html.theme--catppuccin-latte .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:focus:not(:active),html.theme--catppuccin-latte .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .button.is-white:active,html.theme--catppuccin-latte .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-latte .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted:hover,html.theme--catppuccin-latte .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-latte .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-white.is-outlined:hover,html.theme--catppuccin-latte .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-white.is-outlined:focus,html.theme--catppuccin-latte .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:hover,html.theme--catppuccin-latte .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:focus,html.theme--catppuccin-latte .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:focus:not(:active),html.theme--catppuccin-latte .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .button.is-black:active,html.theme--catppuccin-latte .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-latte .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted:hover,html.theme--catppuccin-latte .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-outlined:hover,html.theme--catppuccin-latte .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-black.is-outlined:focus,html.theme--catppuccin-latte .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:hover,html.theme--catppuccin-latte .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:focus,html.theme--catppuccin-latte .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:focus:not(:active),html.theme--catppuccin-latte .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .button.is-light:active,html.theme--catppuccin-latte .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-latte .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted:hover,html.theme--catppuccin-latte .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-outlined:hover,html.theme--catppuccin-latte .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-light.is-outlined:focus,html.theme--catppuccin-latte .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark,html.theme--catppuccin-latte .content kbd.button{background-color:#ccd0da;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:hover,html.theme--catppuccin-latte .content kbd.button:hover,html.theme--catppuccin-latte .button.is-dark.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-hovered{background-color:#c5c9d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:focus,html.theme--catppuccin-latte .content kbd.button:focus,html.theme--catppuccin-latte .button.is-dark.is-focused,html.theme--catppuccin-latte .content kbd.button.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:focus:not(:active),html.theme--catppuccin-latte .content kbd.button:focus:not(:active),html.theme--catppuccin-latte .button.is-dark.is-focused:not(:active),html.theme--catppuccin-latte .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .button.is-dark:active,html.theme--catppuccin-latte .content kbd.button:active,html.theme--catppuccin-latte .button.is-dark.is-active,html.theme--catppuccin-latte .content kbd.button.is-active{background-color:#bdc2cf;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark[disabled],html.theme--catppuccin-latte .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button{background-color:#ccd0da;border-color:#ccd0da;box-shadow:none}html.theme--catppuccin-latte .button.is-dark.is-inverted,html.theme--catppuccin-latte .content kbd.button.is-inverted{background-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted:hover,html.theme--catppuccin-latte .content kbd.button.is-inverted:hover,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-inverted[disabled],html.theme--catppuccin-latte .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-loading::after,html.theme--catppuccin-latte .content kbd.button.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-dark.is-outlined,html.theme--catppuccin-latte .content kbd.button.is-outlined{background-color:transparent;border-color:#ccd0da;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-outlined:hover,html.theme--catppuccin-latte .content kbd.button.is-outlined:hover,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-dark.is-outlined:focus,html.theme--catppuccin-latte .content kbd.button.is-outlined:focus,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-focused{background-color:#ccd0da;border-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #ccd0da #ccd0da !important}html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-dark.is-outlined[disabled],html.theme--catppuccin-latte .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-outlined{background-color:transparent;border-color:#ccd0da;box-shadow:none;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ccd0da #ccd0da !important}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-primary,html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:hover,html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-hovered,html.theme--catppuccin-latte details.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:focus,html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-focused,html.theme--catppuccin-latte details.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:focus:not(:active),html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-latte .button.is-primary.is-focused:not(:active),html.theme--catppuccin-latte details.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button.is-primary:active,html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-latte .button.is-primary.is-active,html.theme--catppuccin-latte details.docstring>section>a.button.is-active.docs-sourcelink{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary[disabled],html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary,fieldset[disabled] html.theme--catppuccin-latte details.docstring>section>a.button.docs-sourcelink{background-color:#1e66f5;border-color:#1e66f5;box-shadow:none}html.theme--catppuccin-latte .button.is-primary.is-inverted,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted:hover,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-primary.is-inverted[disabled],html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-loading::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-primary.is-outlined,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-outlined:hover,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-latte .button.is-primary.is-outlined:focus,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-primary.is-outlined[disabled],html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-latte details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#1e66f5;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-latte details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-light,html.theme--catppuccin-latte details.docstring>section>a.button.is-light.docs-sourcelink{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .button.is-primary.is-light:hover,html.theme--catppuccin-latte details.docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-light.is-hovered,html.theme--catppuccin-latte details.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#dfe9fe;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-primary.is-light:active,html.theme--catppuccin-latte details.docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-latte .button.is-primary.is-light.is-active,html.theme--catppuccin-latte details.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d3e1fd;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-link{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:hover,html.theme--catppuccin-latte .button.is-link.is-hovered{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:focus,html.theme--catppuccin-latte .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:focus:not(:active),html.theme--catppuccin-latte .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button.is-link:active,html.theme--catppuccin-latte .button.is-link.is-active{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link{background-color:#1e66f5;border-color:#1e66f5;box-shadow:none}html.theme--catppuccin-latte .button.is-link.is-inverted{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted:hover,html.theme--catppuccin-latte .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-link.is-outlined{background-color:transparent;border-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-outlined:hover,html.theme--catppuccin-latte .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-link.is-outlined:focus,html.theme--catppuccin-latte .button.is-link.is-outlined.is-focused{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-outlined{background-color:transparent;border-color:#1e66f5;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-link.is-light{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .button.is-link.is-light:hover,html.theme--catppuccin-latte .button.is-link.is-light.is-hovered{background-color:#dfe9fe;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-link.is-light:active,html.theme--catppuccin-latte .button.is-link.is-light.is-active{background-color:#d3e1fd;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-info{background-color:#179299;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:hover,html.theme--catppuccin-latte .button.is-info.is-hovered{background-color:#15878e;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:focus,html.theme--catppuccin-latte .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:focus:not(:active),html.theme--catppuccin-latte .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .button.is-info:active,html.theme--catppuccin-latte .button.is-info.is-active{background-color:#147d83;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info{background-color:#179299;border-color:#179299;box-shadow:none}html.theme--catppuccin-latte .button.is-info.is-inverted{background-color:#fff;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted:hover,html.theme--catppuccin-latte .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#179299}html.theme--catppuccin-latte .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-info.is-outlined{background-color:transparent;border-color:#179299;color:#179299}html.theme--catppuccin-latte .button.is-info.is-outlined:hover,html.theme--catppuccin-latte .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-info.is-outlined:focus,html.theme--catppuccin-latte .button.is-info.is-outlined.is-focused{background-color:#179299;border-color:#179299;color:#fff}html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #179299 #179299 !important}html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-outlined{background-color:transparent;border-color:#179299;box-shadow:none;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #179299 #179299 !important}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-info.is-light{background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .button.is-info.is-light:hover,html.theme--catppuccin-latte .button.is-info.is-light.is-hovered{background-color:#e2f9fb;border-color:transparent;color:#1cb2ba}html.theme--catppuccin-latte .button.is-info.is-light:active,html.theme--catppuccin-latte .button.is-info.is-light.is-active{background-color:#d7f7f9;border-color:transparent;color:#1cb2ba}html.theme--catppuccin-latte .button.is-success{background-color:#40a02b;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:hover,html.theme--catppuccin-latte .button.is-success.is-hovered{background-color:#3c9628;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:focus,html.theme--catppuccin-latte .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:focus:not(:active),html.theme--catppuccin-latte .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .button.is-success:active,html.theme--catppuccin-latte .button.is-success.is-active{background-color:#388c26;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success{background-color:#40a02b;border-color:#40a02b;box-shadow:none}html.theme--catppuccin-latte .button.is-success.is-inverted{background-color:#fff;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted:hover,html.theme--catppuccin-latte .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-success.is-outlined{background-color:transparent;border-color:#40a02b;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-outlined:hover,html.theme--catppuccin-latte .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-success.is-outlined:focus,html.theme--catppuccin-latte .button.is-success.is-outlined.is-focused{background-color:#40a02b;border-color:#40a02b;color:#fff}html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #40a02b #40a02b !important}html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-outlined{background-color:transparent;border-color:#40a02b;box-shadow:none;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #40a02b #40a02b !important}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-success.is-light{background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .button.is-success.is-light:hover,html.theme--catppuccin-latte .button.is-success.is-light.is-hovered{background-color:#e8f8e5;border-color:transparent;color:#40a12b}html.theme--catppuccin-latte .button.is-success.is-light:active,html.theme--catppuccin-latte .button.is-success.is-light.is-active{background-color:#e0f5db;border-color:transparent;color:#40a12b}html.theme--catppuccin-latte .button.is-warning{background-color:#df8e1d;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:hover,html.theme--catppuccin-latte .button.is-warning.is-hovered{background-color:#d4871c;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:focus,html.theme--catppuccin-latte .button.is-warning.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:focus:not(:active),html.theme--catppuccin-latte .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .button.is-warning:active,html.theme--catppuccin-latte .button.is-warning.is-active{background-color:#c8801a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning{background-color:#df8e1d;border-color:#df8e1d;box-shadow:none}html.theme--catppuccin-latte .button.is-warning.is-inverted{background-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted:hover,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-warning.is-outlined{background-color:transparent;border-color:#df8e1d;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-outlined:hover,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-warning.is-outlined:focus,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-focused{background-color:#df8e1d;border-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #df8e1d #df8e1d !important}html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-outlined{background-color:transparent;border-color:#df8e1d;box-shadow:none;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #df8e1d #df8e1d !important}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-light{background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .button.is-warning.is-light:hover,html.theme--catppuccin-latte .button.is-warning.is-light.is-hovered{background-color:#fbf1e2;border-color:transparent;color:#9e6515}html.theme--catppuccin-latte .button.is-warning.is-light:active,html.theme--catppuccin-latte .button.is-warning.is-light.is-active{background-color:#faebd6;border-color:transparent;color:#9e6515}html.theme--catppuccin-latte .button.is-danger{background-color:#d20f39;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:hover,html.theme--catppuccin-latte .button.is-danger.is-hovered{background-color:#c60e36;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:focus,html.theme--catppuccin-latte .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:focus:not(:active),html.theme--catppuccin-latte .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .button.is-danger:active,html.theme--catppuccin-latte .button.is-danger.is-active{background-color:#ba0d33;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger{background-color:#d20f39;border-color:#d20f39;box-shadow:none}html.theme--catppuccin-latte .button.is-danger.is-inverted{background-color:#fff;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted:hover,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-danger.is-outlined{background-color:transparent;border-color:#d20f39;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-outlined:hover,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-danger.is-outlined:focus,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-focused{background-color:#d20f39;border-color:#d20f39;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #d20f39 #d20f39 !important}html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-outlined{background-color:transparent;border-color:#d20f39;box-shadow:none;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #d20f39 #d20f39 !important}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-light{background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .button.is-danger.is-light:hover,html.theme--catppuccin-latte .button.is-danger.is-light.is-hovered{background-color:#fde0e6;border-color:transparent;color:#e9113f}html.theme--catppuccin-latte .button.is-danger.is-light:active,html.theme--catppuccin-latte .button.is-danger.is-light.is-active{background-color:#fcd4dd;border-color:transparent;color:#e9113f}html.theme--catppuccin-latte .button.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-latte .button.is-small:not(.is-rounded),html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-latte .button.is-normal{font-size:1rem}html.theme--catppuccin-latte .button.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .button.is-large{font-size:1.5rem}html.theme--catppuccin-latte .button[disabled],fieldset[disabled] html.theme--catppuccin-latte .button{background-color:#9ca0b0;border-color:#acb0be;box-shadow:none;opacity:.5}html.theme--catppuccin-latte .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-latte .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-latte .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-latte .button.is-static{background-color:#e6e9ef;border-color:#acb0be;color:#8c8fa1;box-shadow:none;pointer-events:none}html.theme--catppuccin-latte .button.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-latte .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-latte .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-latte .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-latte .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-latte .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-latte .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-latte .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-latte .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-latte .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-latte .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-latte .buttons.has-addons .button:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-latte .buttons.has-addons .button:focus,html.theme--catppuccin-latte .buttons.has-addons .button.is-focused,html.theme--catppuccin-latte .buttons.has-addons .button:active,html.theme--catppuccin-latte .buttons.has-addons .button.is-active,html.theme--catppuccin-latte .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-latte .buttons.has-addons .button:focus:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-latte .buttons.has-addons .button:active:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-latte .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .buttons.is-centered{justify-content:center}html.theme--catppuccin-latte .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-latte .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-latte .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .button.is-responsive.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-latte .button.is-responsive,html.theme--catppuccin-latte .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-latte .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-latte .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .button.is-responsive.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-latte .button.is-responsive,html.theme--catppuccin-latte .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-latte .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-latte .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-latte .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-latte .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-latte .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-latte .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-latte .content li+li{margin-top:0.25em}html.theme--catppuccin-latte .content p:not(:last-child),html.theme--catppuccin-latte .content dl:not(:last-child),html.theme--catppuccin-latte .content ol:not(:last-child),html.theme--catppuccin-latte .content ul:not(:last-child),html.theme--catppuccin-latte .content blockquote:not(:last-child),html.theme--catppuccin-latte .content pre:not(:last-child),html.theme--catppuccin-latte .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-latte .content h1,html.theme--catppuccin-latte .content h2,html.theme--catppuccin-latte .content h3,html.theme--catppuccin-latte .content h4,html.theme--catppuccin-latte .content h5,html.theme--catppuccin-latte .content h6{color:#4c4f69;font-weight:600;line-height:1.125}html.theme--catppuccin-latte .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-latte .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-latte .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-latte .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-latte .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-latte .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-latte .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-latte .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-latte .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-latte .content blockquote{background-color:#e6e9ef;border-left:5px solid #acb0be;padding:1.25em 1.5em}html.theme--catppuccin-latte .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-latte .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-latte .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-latte .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-latte .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-latte .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-latte .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-latte .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-latte .content ul ul ul{list-style-type:square}html.theme--catppuccin-latte .content dd{margin-left:2em}html.theme--catppuccin-latte .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-latte .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-latte .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-latte .content figure img{display:inline-block}html.theme--catppuccin-latte .content figure figcaption{font-style:italic}html.theme--catppuccin-latte .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-latte .content sup,html.theme--catppuccin-latte .content sub{font-size:75%}html.theme--catppuccin-latte .content table{width:100%}html.theme--catppuccin-latte .content table td,html.theme--catppuccin-latte .content table th{border:1px solid #acb0be;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-latte .content table th{color:#41445a}html.theme--catppuccin-latte .content table th:not([align]){text-align:inherit}html.theme--catppuccin-latte .content table thead td,html.theme--catppuccin-latte .content table thead th{border-width:0 0 2px;color:#41445a}html.theme--catppuccin-latte .content table tfoot td,html.theme--catppuccin-latte .content table tfoot th{border-width:2px 0 0;color:#41445a}html.theme--catppuccin-latte .content table tbody tr:last-child td,html.theme--catppuccin-latte .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-latte .content .tabs li+li{margin-top:0}html.theme--catppuccin-latte .content.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-latte .content.is-normal{font-size:1rem}html.theme--catppuccin-latte .content.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .content.is-large{font-size:1.5rem}html.theme--catppuccin-latte .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-latte .icon.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-latte .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-latte .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-latte .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-latte .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-latte .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-latte div.icon-text{display:flex}html.theme--catppuccin-latte .image,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-latte .image img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-latte .image img.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-latte .image.is-fullwidth,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-latte .image.is-square img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-latte .image.is-square .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-latte .image.is-1by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-latte .image.is-1by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-latte .image.is-5by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-latte .image.is-5by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-latte .image.is-4by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-latte .image.is-4by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-latte .image.is-3by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-latte .image.is-5by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-latte .image.is-5by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-latte .image.is-16by9 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-latte .image.is-16by9 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-latte .image.is-2by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-latte .image.is-2by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-latte .image.is-3by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-latte .image.is-3by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-latte .image.is-4by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-latte .image.is-4by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-latte .image.is-3by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-latte .image.is-3by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-latte .image.is-2by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-latte .image.is-2by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-latte .image.is-3by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-latte .image.is-9by16 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-latte .image.is-9by16 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-latte .image.is-1by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-latte .image.is-1by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-latte .image.is-1by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-latte .image.is-1by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-latte .image.is-square,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-latte .image.is-1by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-latte .image.is-5by4,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-latte .image.is-4by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-latte .image.is-3by2,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-latte .image.is-5by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-latte .image.is-16by9,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-latte .image.is-2by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-latte .image.is-3by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-latte .image.is-4by5,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-latte .image.is-3by4,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-latte .image.is-2by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-latte .image.is-3by5,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-latte .image.is-9by16,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-latte .image.is-1by2,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-latte .image.is-1by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-latte .image.is-16x16,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-latte .image.is-24x24,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-latte .image.is-32x32,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-latte .image.is-48x48,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-latte .image.is-64x64,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-latte .image.is-96x96,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-latte .image.is-128x128,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-latte .notification{background-color:#e6e9ef;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-latte .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-latte .notification strong{color:currentColor}html.theme--catppuccin-latte .notification code,html.theme--catppuccin-latte .notification pre{background:#fff}html.theme--catppuccin-latte .notification pre code{background:transparent}html.theme--catppuccin-latte .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-latte .notification .title,html.theme--catppuccin-latte .notification .subtitle,html.theme--catppuccin-latte .notification .content{color:currentColor}html.theme--catppuccin-latte .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .notification.is-dark,html.theme--catppuccin-latte .content kbd.notification{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .notification.is-primary,html.theme--catppuccin-latte details.docstring>section>a.notification.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .notification.is-primary.is-light,html.theme--catppuccin-latte details.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .notification.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .notification.is-link.is-light{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .notification.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .notification.is-info.is-light{background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .notification.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .notification.is-success.is-light{background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .notification.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .notification.is-warning.is-light{background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .notification.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .notification.is-danger.is-light{background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-latte .progress::-webkit-progress-bar{background-color:#bcc0cc}html.theme--catppuccin-latte .progress::-webkit-progress-value{background-color:#8c8fa1}html.theme--catppuccin-latte .progress::-moz-progress-bar{background-color:#8c8fa1}html.theme--catppuccin-latte .progress::-ms-fill{background-color:#8c8fa1;border:none}html.theme--catppuccin-latte .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-latte .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-latte .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-latte .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-latte .content kbd.progress::-webkit-progress-value{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-latte .content kbd.progress::-moz-progress-bar{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark::-ms-fill,html.theme--catppuccin-latte .content kbd.progress::-ms-fill{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark:indeterminate,html.theme--catppuccin-latte .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #ccd0da 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-latte details.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-latte details.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary::-ms-fill,html.theme--catppuccin-latte details.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary:indeterminate,html.theme--catppuccin-latte details.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #1e66f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-link::-webkit-progress-value{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link::-moz-progress-bar{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link::-ms-fill{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1e66f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-info::-webkit-progress-value{background-color:#179299}html.theme--catppuccin-latte .progress.is-info::-moz-progress-bar{background-color:#179299}html.theme--catppuccin-latte .progress.is-info::-ms-fill{background-color:#179299}html.theme--catppuccin-latte .progress.is-info:indeterminate{background-image:linear-gradient(to right, #179299 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-success::-webkit-progress-value{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success::-moz-progress-bar{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success::-ms-fill{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success:indeterminate{background-image:linear-gradient(to right, #40a02b 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-warning::-webkit-progress-value{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning::-moz-progress-bar{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning::-ms-fill{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #df8e1d 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-danger::-webkit-progress-value{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger::-moz-progress-bar{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger::-ms-fill{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #d20f39 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#bcc0cc;background-image:linear-gradient(to right, #4c4f69 30%, #bcc0cc 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-latte .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-latte .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-latte .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-latte .progress.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-latte .progress.is-medium{height:1.25rem}html.theme--catppuccin-latte .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-latte .table{background-color:#bcc0cc;color:#4c4f69}html.theme--catppuccin-latte .table td,html.theme--catppuccin-latte .table th{border:1px solid #acb0be;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-latte .table td.is-white,html.theme--catppuccin-latte .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .table td.is-black,html.theme--catppuccin-latte .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .table td.is-light,html.theme--catppuccin-latte .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .table td.is-dark,html.theme--catppuccin-latte .table th.is-dark{background-color:#ccd0da;border-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .table td.is-primary,html.theme--catppuccin-latte .table th.is-primary{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-link,html.theme--catppuccin-latte .table th.is-link{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-info,html.theme--catppuccin-latte .table th.is-info{background-color:#179299;border-color:#179299;color:#fff}html.theme--catppuccin-latte .table td.is-success,html.theme--catppuccin-latte .table th.is-success{background-color:#40a02b;border-color:#40a02b;color:#fff}html.theme--catppuccin-latte .table td.is-warning,html.theme--catppuccin-latte .table th.is-warning{background-color:#df8e1d;border-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .table td.is-danger,html.theme--catppuccin-latte .table th.is-danger{background-color:#d20f39;border-color:#d20f39;color:#fff}html.theme--catppuccin-latte .table td.is-narrow,html.theme--catppuccin-latte .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-latte .table td.is-selected,html.theme--catppuccin-latte .table th.is-selected{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-selected a,html.theme--catppuccin-latte .table td.is-selected strong,html.theme--catppuccin-latte .table th.is-selected a,html.theme--catppuccin-latte .table th.is-selected strong{color:currentColor}html.theme--catppuccin-latte .table td.is-vcentered,html.theme--catppuccin-latte .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-latte .table th{color:#41445a}html.theme--catppuccin-latte .table th:not([align]){text-align:left}html.theme--catppuccin-latte .table tr.is-selected{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table tr.is-selected a,html.theme--catppuccin-latte .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-latte .table tr.is-selected td,html.theme--catppuccin-latte .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-latte .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table thead td,html.theme--catppuccin-latte .table thead th{border-width:0 0 2px;color:#41445a}html.theme--catppuccin-latte .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table tfoot td,html.theme--catppuccin-latte .table tfoot th{border-width:2px 0 0;color:#41445a}html.theme--catppuccin-latte .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table tbody tr:last-child td,html.theme--catppuccin-latte .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-latte .table.is-bordered td,html.theme--catppuccin-latte .table.is-bordered th{border-width:1px}html.theme--catppuccin-latte .table.is-bordered tr:last-child td,html.theme--catppuccin-latte .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-latte .table.is-fullwidth{width:100%}html.theme--catppuccin-latte .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#ccd0da}html.theme--catppuccin-latte .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#ccd0da}html.theme--catppuccin-latte .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#d2d5de}html.theme--catppuccin-latte .table.is-narrow td,html.theme--catppuccin-latte .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-latte .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#ccd0da}html.theme--catppuccin-latte .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-latte .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .tags .tag,html.theme--catppuccin-latte .tags .content kbd,html.theme--catppuccin-latte .content .tags kbd,html.theme--catppuccin-latte .tags details.docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-latte .tags .tag:not(:last-child),html.theme--catppuccin-latte .tags .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags kbd:not(:last-child),html.theme--catppuccin-latte .tags details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-latte .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-latte .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-latte .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .tags.are-medium details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-latte .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .tags.are-large details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-latte .tags.is-centered{justify-content:center}html.theme--catppuccin-latte .tags.is-centered .tag,html.theme--catppuccin-latte .tags.is-centered .content kbd,html.theme--catppuccin-latte .content .tags.is-centered kbd,html.theme--catppuccin-latte .tags.is-centered details.docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-latte .tags.is-right{justify-content:flex-end}html.theme--catppuccin-latte .tags.is-right .tag:not(:first-child),html.theme--catppuccin-latte .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-latte .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-latte .tags.is-right details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-latte .tags.is-right .tag:not(:last-child),html.theme--catppuccin-latte .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-latte .tags.is-right details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-latte .tags.has-addons .tag,html.theme--catppuccin-latte .tags.has-addons .content kbd,html.theme--catppuccin-latte .content .tags.has-addons kbd,html.theme--catppuccin-latte .tags.has-addons details.docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-latte .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-latte .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-latte .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-latte .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-latte .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-latte .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-latte .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-latte .tag:not(body),html.theme--catppuccin-latte .content kbd:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#e6e9ef;border-radius:.4em;color:#4c4f69;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-latte .tag:not(body) .delete,html.theme--catppuccin-latte .content kbd:not(body) .delete,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-latte .tag.is-white:not(body),html.theme--catppuccin-latte .content kbd.is-white:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .tag.is-black:not(body),html.theme--catppuccin-latte .content kbd.is-black:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .tag.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .tag.is-dark:not(body),html.theme--catppuccin-latte .content kbd:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-latte .content details.docstring>section>kbd:not(body){background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .tag.is-primary:not(body),html.theme--catppuccin-latte .content kbd.is-primary:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:not(body){background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .tag.is-primary.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .tag.is-link:not(body),html.theme--catppuccin-latte .content kbd.is-link:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .tag.is-link.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-link.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .tag.is-info:not(body),html.theme--catppuccin-latte .content kbd.is-info:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#179299;color:#fff}html.theme--catppuccin-latte .tag.is-info.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-info.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .tag.is-success:not(body),html.theme--catppuccin-latte .content kbd.is-success:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .tag.is-success.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-success.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .tag.is-warning:not(body),html.theme--catppuccin-latte .content kbd.is-warning:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .tag.is-warning.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .tag.is-danger:not(body),html.theme--catppuccin-latte .content kbd.is-danger:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .tag.is-danger.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .tag.is-normal:not(body),html.theme--catppuccin-latte .content kbd.is-normal:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-latte .tag.is-medium:not(body),html.theme--catppuccin-latte .content kbd.is-medium:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-latte .tag.is-large:not(body),html.theme--catppuccin-latte .content kbd.is-large:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-latte .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-latte .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-latte .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-latte .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-latte .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-latte .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-latte .tag.is-delete:not(body),html.theme--catppuccin-latte .content kbd.is-delete:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-latte .tag.is-delete:not(body)::before,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::before,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-latte .tag.is-delete:not(body)::after,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::after,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-latte .tag.is-delete:not(body)::before,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::before,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-latte .tag.is-delete:not(body)::after,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::after,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-latte .tag.is-delete:not(body):hover,html.theme--catppuccin-latte .content kbd.is-delete:not(body):hover,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-latte .tag.is-delete:not(body):focus,html.theme--catppuccin-latte .content kbd.is-delete:not(body):focus,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#d6dbe5}html.theme--catppuccin-latte .tag.is-delete:not(body):active,html.theme--catppuccin-latte .content kbd.is-delete:not(body):active,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#c7cedb}html.theme--catppuccin-latte .tag.is-rounded:not(body),html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-latte .content kbd.is-rounded:not(body),html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-latte a.tag:hover,html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-latte .title,html.theme--catppuccin-latte .subtitle{word-break:break-word}html.theme--catppuccin-latte .title em,html.theme--catppuccin-latte .title span,html.theme--catppuccin-latte .subtitle em,html.theme--catppuccin-latte .subtitle span{font-weight:inherit}html.theme--catppuccin-latte .title sub,html.theme--catppuccin-latte .subtitle sub{font-size:.75em}html.theme--catppuccin-latte .title sup,html.theme--catppuccin-latte .subtitle sup{font-size:.75em}html.theme--catppuccin-latte .title .tag,html.theme--catppuccin-latte .title .content kbd,html.theme--catppuccin-latte .content .title kbd,html.theme--catppuccin-latte .title details.docstring>section>a.docs-sourcelink,html.theme--catppuccin-latte .subtitle .tag,html.theme--catppuccin-latte .subtitle .content kbd,html.theme--catppuccin-latte .content .subtitle kbd,html.theme--catppuccin-latte .subtitle details.docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-latte .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-latte .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-latte .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-latte .title.is-1{font-size:3rem}html.theme--catppuccin-latte .title.is-2{font-size:2.5rem}html.theme--catppuccin-latte .title.is-3{font-size:2rem}html.theme--catppuccin-latte .title.is-4{font-size:1.5rem}html.theme--catppuccin-latte .title.is-5{font-size:1.25rem}html.theme--catppuccin-latte .title.is-6{font-size:1rem}html.theme--catppuccin-latte .title.is-7{font-size:.75rem}html.theme--catppuccin-latte .subtitle{color:#9ca0b0;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-latte .subtitle strong{color:#9ca0b0;font-weight:600}html.theme--catppuccin-latte .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-latte .subtitle.is-1{font-size:3rem}html.theme--catppuccin-latte .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-latte .subtitle.is-3{font-size:2rem}html.theme--catppuccin-latte .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-latte .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-latte .subtitle.is-6{font-size:1rem}html.theme--catppuccin-latte .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-latte .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-latte .number{align-items:center;background-color:#e6e9ef;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{background-color:#eff1f5;border-color:#acb0be;border-radius:.4em;color:#8c8fa1}html.theme--catppuccin-latte .select select::-moz-placeholder,html.theme--catppuccin-latte .textarea::-moz-placeholder,html.theme--catppuccin-latte .input::-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-latte .select select::-webkit-input-placeholder,html.theme--catppuccin-latte .textarea::-webkit-input-placeholder,html.theme--catppuccin-latte .input::-webkit-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:-moz-placeholder,html.theme--catppuccin-latte .textarea:-moz-placeholder,html.theme--catppuccin-latte .input:-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:-ms-input-placeholder,html.theme--catppuccin-latte .textarea:-ms-input-placeholder,html.theme--catppuccin-latte .input:-ms-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:hover,html.theme--catppuccin-latte .textarea:hover,html.theme--catppuccin-latte .input:hover,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-latte .select select.is-hovered,html.theme--catppuccin-latte .is-hovered.textarea,html.theme--catppuccin-latte .is-hovered.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#9ca0b0}html.theme--catppuccin-latte .select select:focus,html.theme--catppuccin-latte .textarea:focus,html.theme--catppuccin-latte .input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-latte .select select.is-focused,html.theme--catppuccin-latte .is-focused.textarea,html.theme--catppuccin-latte .is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .select select:active,html.theme--catppuccin-latte .textarea:active,html.theme--catppuccin-latte .input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-latte .select select.is-active,html.theme--catppuccin-latte .is-active.textarea,html.theme--catppuccin-latte .is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1e66f5;box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select select[disabled],html.theme--catppuccin-latte .textarea[disabled],html.theme--catppuccin-latte .input[disabled],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-latte .select select,fieldset[disabled] html.theme--catppuccin-latte .textarea,fieldset[disabled] html.theme--catppuccin-latte .input,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{background-color:#9ca0b0;border-color:#e6e9ef;box-shadow:none;color:#616587}html.theme--catppuccin-latte .select select[disabled]::-moz-placeholder,html.theme--catppuccin-latte .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-latte .input[disabled]::-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]:-moz-placeholder,html.theme--catppuccin-latte .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-latte .input[disabled]:-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-latte .textarea[readonly],html.theme--catppuccin-latte .input[readonly],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-latte .is-white.textarea,html.theme--catppuccin-latte .is-white.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-latte .is-white.textarea:focus,html.theme--catppuccin-latte .is-white.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-latte .is-white.is-focused.textarea,html.theme--catppuccin-latte .is-white.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-white.textarea:active,html.theme--catppuccin-latte .is-white.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-latte .is-white.is-active.textarea,html.theme--catppuccin-latte .is-white.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .is-black.textarea,html.theme--catppuccin-latte .is-black.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-latte .is-black.textarea:focus,html.theme--catppuccin-latte .is-black.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-latte .is-black.is-focused.textarea,html.theme--catppuccin-latte .is-black.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-black.textarea:active,html.theme--catppuccin-latte .is-black.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-latte .is-black.is-active.textarea,html.theme--catppuccin-latte .is-black.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .is-light.textarea,html.theme--catppuccin-latte .is-light.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-latte .is-light.textarea:focus,html.theme--catppuccin-latte .is-light.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-latte .is-light.is-focused.textarea,html.theme--catppuccin-latte .is-light.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-light.textarea:active,html.theme--catppuccin-latte .is-light.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-latte .is-light.is-active.textarea,html.theme--catppuccin-latte .is-light.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .is-dark.textarea,html.theme--catppuccin-latte .content kbd.textarea,html.theme--catppuccin-latte .is-dark.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-latte .content kbd.input{border-color:#ccd0da}html.theme--catppuccin-latte .is-dark.textarea:focus,html.theme--catppuccin-latte .content kbd.textarea:focus,html.theme--catppuccin-latte .is-dark.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-latte .content kbd.input:focus,html.theme--catppuccin-latte .is-dark.is-focused.textarea,html.theme--catppuccin-latte .content kbd.is-focused.textarea,html.theme--catppuccin-latte .is-dark.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .content kbd.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-dark.textarea:active,html.theme--catppuccin-latte .content kbd.textarea:active,html.theme--catppuccin-latte .is-dark.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-latte .content kbd.input:active,html.theme--catppuccin-latte .is-dark.is-active.textarea,html.theme--catppuccin-latte .content kbd.is-active.textarea,html.theme--catppuccin-latte .is-dark.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .content kbd.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .is-primary.textarea,html.theme--catppuccin-latte details.docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-latte details.docstring>section>a.input.docs-sourcelink{border-color:#1e66f5}html.theme--catppuccin-latte .is-primary.textarea:focus,html.theme--catppuccin-latte details.docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-latte .is-primary.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-latte details.docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-latte .is-primary.is-focused.textarea,html.theme--catppuccin-latte details.docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte details.docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-latte .is-primary.textarea:active,html.theme--catppuccin-latte details.docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-latte .is-primary.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-latte details.docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-latte .is-primary.is-active.textarea,html.theme--catppuccin-latte details.docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte details.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .is-link.textarea,html.theme--catppuccin-latte .is-link.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1e66f5}html.theme--catppuccin-latte .is-link.textarea:focus,html.theme--catppuccin-latte .is-link.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-latte .is-link.is-focused.textarea,html.theme--catppuccin-latte .is-link.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-link.textarea:active,html.theme--catppuccin-latte .is-link.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-latte .is-link.is-active.textarea,html.theme--catppuccin-latte .is-link.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .is-info.textarea,html.theme--catppuccin-latte .is-info.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#179299}html.theme--catppuccin-latte .is-info.textarea:focus,html.theme--catppuccin-latte .is-info.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-latte .is-info.is-focused.textarea,html.theme--catppuccin-latte .is-info.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-info.textarea:active,html.theme--catppuccin-latte .is-info.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-latte .is-info.is-active.textarea,html.theme--catppuccin-latte .is-info.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .is-success.textarea,html.theme--catppuccin-latte .is-success.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#40a02b}html.theme--catppuccin-latte .is-success.textarea:focus,html.theme--catppuccin-latte .is-success.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-latte .is-success.is-focused.textarea,html.theme--catppuccin-latte .is-success.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-success.textarea:active,html.theme--catppuccin-latte .is-success.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-latte .is-success.is-active.textarea,html.theme--catppuccin-latte .is-success.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .is-warning.textarea,html.theme--catppuccin-latte .is-warning.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#df8e1d}html.theme--catppuccin-latte .is-warning.textarea:focus,html.theme--catppuccin-latte .is-warning.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-latte .is-warning.is-focused.textarea,html.theme--catppuccin-latte .is-warning.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-warning.textarea:active,html.theme--catppuccin-latte .is-warning.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-latte .is-warning.is-active.textarea,html.theme--catppuccin-latte .is-warning.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .is-danger.textarea,html.theme--catppuccin-latte .is-danger.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#d20f39}html.theme--catppuccin-latte .is-danger.textarea:focus,html.theme--catppuccin-latte .is-danger.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-latte .is-danger.is-focused.textarea,html.theme--catppuccin-latte .is-danger.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-danger.textarea:active,html.theme--catppuccin-latte .is-danger.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-latte .is-danger.is-active.textarea,html.theme--catppuccin-latte .is-danger.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .is-small.textarea,html.theme--catppuccin-latte .is-small.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-latte .is-medium.textarea,html.theme--catppuccin-latte .is-medium.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .is-large.textarea,html.theme--catppuccin-latte .is-large.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-latte .is-fullwidth.textarea,html.theme--catppuccin-latte .is-fullwidth.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-latte .is-inline.textarea,html.theme--catppuccin-latte .is-inline.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-latte .input.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-latte .input.is-static,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-latte .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-latte .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-latte .textarea[rows]{height:initial}html.theme--catppuccin-latte .textarea.has-fixed-size{resize:none}html.theme--catppuccin-latte .radio,html.theme--catppuccin-latte .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-latte .radio input,html.theme--catppuccin-latte .checkbox input{cursor:pointer}html.theme--catppuccin-latte .radio:hover,html.theme--catppuccin-latte .checkbox:hover{color:#04a5e5}html.theme--catppuccin-latte .radio[disabled],html.theme--catppuccin-latte .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-latte .radio,fieldset[disabled] html.theme--catppuccin-latte .checkbox,html.theme--catppuccin-latte .radio input[disabled],html.theme--catppuccin-latte .checkbox input[disabled]{color:#616587;cursor:not-allowed}html.theme--catppuccin-latte .radio+.radio{margin-left:.5em}html.theme--catppuccin-latte .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-latte .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading)::after{border-color:#1e66f5;right:1.125em;z-index:4}html.theme--catppuccin-latte .select.is-rounded select,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-latte .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-latte .select select::-ms-expand{display:none}html.theme--catppuccin-latte .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-latte .select select:hover{border-color:#e6e9ef}html.theme--catppuccin-latte .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-latte .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-latte .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#04a5e5}html.theme--catppuccin-latte .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-latte .select.is-white select{border-color:#fff}html.theme--catppuccin-latte .select.is-white select:hover,html.theme--catppuccin-latte .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-latte .select.is-white select:focus,html.theme--catppuccin-latte .select.is-white select.is-focused,html.theme--catppuccin-latte .select.is-white select:active,html.theme--catppuccin-latte .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-latte .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-latte .select.is-black select:hover,html.theme--catppuccin-latte .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-latte .select.is-black select:focus,html.theme--catppuccin-latte .select.is-black select.is-focused,html.theme--catppuccin-latte .select.is-black select:active,html.theme--catppuccin-latte .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-latte .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-latte .select.is-light select:hover,html.theme--catppuccin-latte .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-latte .select.is-light select:focus,html.theme--catppuccin-latte .select.is-light select.is-focused,html.theme--catppuccin-latte .select.is-light select:active,html.theme--catppuccin-latte .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .select.is-dark:not(:hover)::after,html.theme--catppuccin-latte .content kbd.select:not(:hover)::after{border-color:#ccd0da}html.theme--catppuccin-latte .select.is-dark select,html.theme--catppuccin-latte .content kbd.select select{border-color:#ccd0da}html.theme--catppuccin-latte .select.is-dark select:hover,html.theme--catppuccin-latte .content kbd.select select:hover,html.theme--catppuccin-latte .select.is-dark select.is-hovered,html.theme--catppuccin-latte .content kbd.select select.is-hovered{border-color:#bdc2cf}html.theme--catppuccin-latte .select.is-dark select:focus,html.theme--catppuccin-latte .content kbd.select select:focus,html.theme--catppuccin-latte .select.is-dark select.is-focused,html.theme--catppuccin-latte .content kbd.select select.is-focused,html.theme--catppuccin-latte .select.is-dark select:active,html.theme--catppuccin-latte .content kbd.select select:active,html.theme--catppuccin-latte .select.is-dark select.is-active,html.theme--catppuccin-latte .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .select.is-primary:not(:hover)::after,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-primary select,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-primary select:hover,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-latte .select.is-primary select.is-hovered,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#0b57ef}html.theme--catppuccin-latte .select.is-primary select:focus,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-latte .select.is-primary select.is-focused,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-latte .select.is-primary select:active,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-latte .select.is-primary select.is-active,html.theme--catppuccin-latte details.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select.is-link:not(:hover)::after{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-link select{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-link select:hover,html.theme--catppuccin-latte .select.is-link select.is-hovered{border-color:#0b57ef}html.theme--catppuccin-latte .select.is-link select:focus,html.theme--catppuccin-latte .select.is-link select.is-focused,html.theme--catppuccin-latte .select.is-link select:active,html.theme--catppuccin-latte .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select.is-info:not(:hover)::after{border-color:#179299}html.theme--catppuccin-latte .select.is-info select{border-color:#179299}html.theme--catppuccin-latte .select.is-info select:hover,html.theme--catppuccin-latte .select.is-info select.is-hovered{border-color:#147d83}html.theme--catppuccin-latte .select.is-info select:focus,html.theme--catppuccin-latte .select.is-info select.is-focused,html.theme--catppuccin-latte .select.is-info select:active,html.theme--catppuccin-latte .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .select.is-success:not(:hover)::after{border-color:#40a02b}html.theme--catppuccin-latte .select.is-success select{border-color:#40a02b}html.theme--catppuccin-latte .select.is-success select:hover,html.theme--catppuccin-latte .select.is-success select.is-hovered{border-color:#388c26}html.theme--catppuccin-latte .select.is-success select:focus,html.theme--catppuccin-latte .select.is-success select.is-focused,html.theme--catppuccin-latte .select.is-success select:active,html.theme--catppuccin-latte .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .select.is-warning:not(:hover)::after{border-color:#df8e1d}html.theme--catppuccin-latte .select.is-warning select{border-color:#df8e1d}html.theme--catppuccin-latte .select.is-warning select:hover,html.theme--catppuccin-latte .select.is-warning select.is-hovered{border-color:#c8801a}html.theme--catppuccin-latte .select.is-warning select:focus,html.theme--catppuccin-latte .select.is-warning select.is-focused,html.theme--catppuccin-latte .select.is-warning select:active,html.theme--catppuccin-latte .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .select.is-danger:not(:hover)::after{border-color:#d20f39}html.theme--catppuccin-latte .select.is-danger select{border-color:#d20f39}html.theme--catppuccin-latte .select.is-danger select:hover,html.theme--catppuccin-latte .select.is-danger select.is-hovered{border-color:#ba0d33}html.theme--catppuccin-latte .select.is-danger select:focus,html.theme--catppuccin-latte .select.is-danger select.is-focused,html.theme--catppuccin-latte .select.is-danger select:active,html.theme--catppuccin-latte .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .select.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-latte .select.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .select.is-large{font-size:1.5rem}html.theme--catppuccin-latte .select.is-disabled::after{border-color:#616587 !important;opacity:0.5}html.theme--catppuccin-latte .select.is-fullwidth{width:100%}html.theme--catppuccin-latte .select.is-fullwidth select{width:100%}html.theme--catppuccin-latte .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-latte .select.is-loading.is-small:after,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-latte .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-latte .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-latte .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-latte .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:hover .file-cta,html.theme--catppuccin-latte .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:focus .file-cta,html.theme--catppuccin-latte .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:active .file-cta,html.theme--catppuccin-latte .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-black:hover .file-cta,html.theme--catppuccin-latte .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-black:focus .file-cta,html.theme--catppuccin-latte .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-latte .file.is-black:active .file-cta,html.theme--catppuccin-latte .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:hover .file-cta,html.theme--catppuccin-latte .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:focus .file-cta,html.theme--catppuccin-latte .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:active .file-cta,html.theme--catppuccin-latte .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark .file-cta,html.theme--catppuccin-latte .content kbd.file .file-cta{background-color:#ccd0da;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:hover .file-cta,html.theme--catppuccin-latte .content kbd.file:hover .file-cta,html.theme--catppuccin-latte .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-latte .content kbd.file.is-hovered .file-cta{background-color:#c5c9d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:focus .file-cta,html.theme--catppuccin-latte .content kbd.file:focus .file-cta,html.theme--catppuccin-latte .file.is-dark.is-focused .file-cta,html.theme--catppuccin-latte .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(204,208,218,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:active .file-cta,html.theme--catppuccin-latte .content kbd.file:active .file-cta,html.theme--catppuccin-latte .file.is-dark.is-active .file-cta,html.theme--catppuccin-latte .content kbd.file.is-active .file-cta{background-color:#bdc2cf;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-primary .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-primary:hover .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-latte .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-primary:focus .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-latte .file.is-primary.is-focused .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(30,102,245,0.25);color:#fff}html.theme--catppuccin-latte .file.is-primary:active .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-latte .file.is-primary.is-active .file-cta,html.theme--catppuccin-latte details.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link .file-cta{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link:hover .file-cta,html.theme--catppuccin-latte .file.is-link.is-hovered .file-cta{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link:focus .file-cta,html.theme--catppuccin-latte .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(30,102,245,0.25);color:#fff}html.theme--catppuccin-latte .file.is-link:active .file-cta,html.theme--catppuccin-latte .file.is-link.is-active .file-cta{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info .file-cta{background-color:#179299;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info:hover .file-cta,html.theme--catppuccin-latte .file.is-info.is-hovered .file-cta{background-color:#15878e;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info:focus .file-cta,html.theme--catppuccin-latte .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(23,146,153,0.25);color:#fff}html.theme--catppuccin-latte .file.is-info:active .file-cta,html.theme--catppuccin-latte .file.is-info.is-active .file-cta{background-color:#147d83;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success .file-cta{background-color:#40a02b;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success:hover .file-cta,html.theme--catppuccin-latte .file.is-success.is-hovered .file-cta{background-color:#3c9628;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success:focus .file-cta,html.theme--catppuccin-latte .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(64,160,43,0.25);color:#fff}html.theme--catppuccin-latte .file.is-success:active .file-cta,html.theme--catppuccin-latte .file.is-success.is-active .file-cta{background-color:#388c26;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning .file-cta{background-color:#df8e1d;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning:hover .file-cta,html.theme--catppuccin-latte .file.is-warning.is-hovered .file-cta{background-color:#d4871c;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning:focus .file-cta,html.theme--catppuccin-latte .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(223,142,29,0.25);color:#fff}html.theme--catppuccin-latte .file.is-warning:active .file-cta,html.theme--catppuccin-latte .file.is-warning.is-active .file-cta{background-color:#c8801a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger .file-cta{background-color:#d20f39;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger:hover .file-cta,html.theme--catppuccin-latte .file.is-danger.is-hovered .file-cta{background-color:#c60e36;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger:focus .file-cta,html.theme--catppuccin-latte .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(210,15,57,0.25);color:#fff}html.theme--catppuccin-latte .file.is-danger:active .file-cta,html.theme--catppuccin-latte .file.is-danger.is-active .file-cta{background-color:#ba0d33;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-latte .file.is-normal{font-size:1rem}html.theme--catppuccin-latte .file.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-latte .file.is-large{font-size:1.5rem}html.theme--catppuccin-latte .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-latte .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-latte .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-latte .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-latte .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-latte .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-latte .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-latte .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-latte .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-latte .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-latte .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-latte .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-latte .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-latte .file.is-centered{justify-content:center}html.theme--catppuccin-latte .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-latte .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-latte .file.is-right{justify-content:flex-end}html.theme--catppuccin-latte .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-latte .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-latte .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-latte .file-label:hover .file-cta{background-color:#c5c9d5;color:#41445a}html.theme--catppuccin-latte .file-label:hover .file-name{border-color:#a5a9b8}html.theme--catppuccin-latte .file-label:active .file-cta{background-color:#bdc2cf;color:#41445a}html.theme--catppuccin-latte .file-label:active .file-name{border-color:#9ea2b3}html.theme--catppuccin-latte .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte .file-name{border-color:#acb0be;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-latte .file-cta{background-color:#ccd0da;color:#4c4f69}html.theme--catppuccin-latte .file-name{border-color:#acb0be;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-latte .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-latte .file-icon .fa{font-size:14px}html.theme--catppuccin-latte .label{color:#41445a;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-latte .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-latte .label.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-latte .label.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .label.is-large{font-size:1.5rem}html.theme--catppuccin-latte .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-latte .help.is-white{color:#fff}html.theme--catppuccin-latte .help.is-black{color:#0a0a0a}html.theme--catppuccin-latte .help.is-light{color:#f5f5f5}html.theme--catppuccin-latte .help.is-dark,html.theme--catppuccin-latte .content kbd.help{color:#ccd0da}html.theme--catppuccin-latte .help.is-primary,html.theme--catppuccin-latte details.docstring>section>a.help.docs-sourcelink{color:#1e66f5}html.theme--catppuccin-latte .help.is-link{color:#1e66f5}html.theme--catppuccin-latte .help.is-info{color:#179299}html.theme--catppuccin-latte .help.is-success{color:#40a02b}html.theme--catppuccin-latte .help.is-warning{color:#df8e1d}html.theme--catppuccin-latte .help.is-danger{color:#d20f39}html.theme--catppuccin-latte .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-latte .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-latte .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-latte .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-latte .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-latte .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-latte .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-latte .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-latte .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-latte .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field.is-horizontal{display:flex}}html.theme--catppuccin-latte .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-latte .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-latte .field-label.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-latte .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-latte .field-body .field{margin-bottom:0}html.theme--catppuccin-latte .field-body>.field{flex-shrink:1}html.theme--catppuccin-latte .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-latte .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-latte .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-latte .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right .select:focus~.icon{color:#ccd0da}html.theme--catppuccin-latte .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-latte .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-latte .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-latte .control.has-icons-left .icon,html.theme--catppuccin-latte .control.has-icons-right .icon{color:#acb0be;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-latte .control.has-icons-left .input,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-latte .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-latte .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-latte .control.has-icons-right .input,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-latte .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-latte .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-latte .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-latte .control.is-loading.is-small:after,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-latte .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-latte .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-latte .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-latte .breadcrumb a{align-items:center;color:#1e66f5;display:initial;justify-content:center;padding:0 .75em}html.theme--catppuccin-latte .breadcrumb a:hover{color:#04a5e5}html.theme--catppuccin-latte .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-latte .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-latte .breadcrumb li.is-active a{color:#41445a;cursor:default;pointer-events:none}html.theme--catppuccin-latte .breadcrumb li+li::before{color:#9ca0b0;content:"\0002f"}html.theme--catppuccin-latte .breadcrumb ul,html.theme--catppuccin-latte .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-latte .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-latte .breadcrumb.is-centered ol,html.theme--catppuccin-latte .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-latte .breadcrumb.is-right ol,html.theme--catppuccin-latte .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-latte .breadcrumb.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-latte .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-latte .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-latte .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-latte .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-latte .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-latte .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#4c4f69;max-width:100%;position:relative}html.theme--catppuccin-latte .card-footer:first-child,html.theme--catppuccin-latte .card-content:first-child,html.theme--catppuccin-latte .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-latte .card-footer:last-child,html.theme--catppuccin-latte .card-content:last-child,html.theme--catppuccin-latte .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-latte .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-latte .card-header-title{align-items:center;color:#41445a;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-latte .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-latte .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-latte .card-image{display:block;position:relative}html.theme--catppuccin-latte .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-latte .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-latte .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-latte .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-latte .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-latte .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-latte .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-latte .dropdown.is-active .dropdown-menu,html.theme--catppuccin-latte .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-latte .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-latte .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-latte .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-latte .dropdown-content{background-color:#e6e9ef;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-latte .dropdown-item{color:#4c4f69;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-latte a.dropdown-item,html.theme--catppuccin-latte button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-latte a.dropdown-item:hover,html.theme--catppuccin-latte button.dropdown-item:hover{background-color:#e6e9ef;color:#0a0a0a}html.theme--catppuccin-latte a.dropdown-item.is-active,html.theme--catppuccin-latte button.dropdown-item.is-active{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-latte .level{align-items:center;justify-content:space-between}html.theme--catppuccin-latte .level code{border-radius:.4em}html.theme--catppuccin-latte .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-latte .level.is-mobile{display:flex}html.theme--catppuccin-latte .level.is-mobile .level-left,html.theme--catppuccin-latte .level.is-mobile .level-right{display:flex}html.theme--catppuccin-latte .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-latte .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-latte .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level{display:flex}html.theme--catppuccin-latte .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-latte .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-latte .level-item .title,html.theme--catppuccin-latte .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-latte .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-latte .level-left,html.theme--catppuccin-latte .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .level-left .level-item.is-flexible,html.theme--catppuccin-latte .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-left .level-item:not(:last-child),html.theme--catppuccin-latte .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-latte .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-latte .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-left{display:flex}}html.theme--catppuccin-latte .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-right{display:flex}}html.theme--catppuccin-latte .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-latte .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-latte .media .media{border-top:1px solid rgba(172,176,190,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-latte .media .media .content:not(:last-child),html.theme--catppuccin-latte .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-latte .media .media .media{padding-top:.5rem}html.theme--catppuccin-latte .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-latte .media+.media{border-top:1px solid rgba(172,176,190,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-latte .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-latte .media-left,html.theme--catppuccin-latte .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .media-left{margin-right:1rem}html.theme--catppuccin-latte .media-right{margin-left:1rem}html.theme--catppuccin-latte .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-latte .media-content{overflow-x:auto}}html.theme--catppuccin-latte .menu{font-size:1rem}html.theme--catppuccin-latte .menu.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-latte .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .menu.is-large{font-size:1.5rem}html.theme--catppuccin-latte .menu-list{line-height:1.25}html.theme--catppuccin-latte .menu-list a{border-radius:3px;color:#4c4f69;display:block;padding:0.5em 0.75em}html.theme--catppuccin-latte .menu-list a:hover{background-color:#e6e9ef;color:#41445a}html.theme--catppuccin-latte .menu-list a.is-active{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .menu-list li ul{border-left:1px solid #acb0be;margin:.75em;padding-left:.75em}html.theme--catppuccin-latte .menu-label{color:#616587;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-latte .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-latte .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-latte .message{background-color:#e6e9ef;border-radius:.4em;font-size:1rem}html.theme--catppuccin-latte .message strong{color:currentColor}html.theme--catppuccin-latte .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-latte .message.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-latte .message.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .message.is-large{font-size:1.5rem}html.theme--catppuccin-latte .message.is-white{background-color:#fff}html.theme--catppuccin-latte .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-latte .message.is-black{background-color:#fafafa}html.theme--catppuccin-latte .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-latte .message.is-light{background-color:#fafafa}html.theme--catppuccin-latte .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-latte .message.is-dark,html.theme--catppuccin-latte .content kbd.message{background-color:#f9fafb}html.theme--catppuccin-latte .message.is-dark .message-header,html.theme--catppuccin-latte .content kbd.message .message-header{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .message.is-dark .message-body,html.theme--catppuccin-latte .content kbd.message .message-body{border-color:#ccd0da}html.theme--catppuccin-latte .message.is-primary,html.theme--catppuccin-latte details.docstring>section>a.message.docs-sourcelink{background-color:#ebf2fe}html.theme--catppuccin-latte .message.is-primary .message-header,html.theme--catppuccin-latte details.docstring>section>a.message.docs-sourcelink .message-header{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .message.is-primary .message-body,html.theme--catppuccin-latte details.docstring>section>a.message.docs-sourcelink .message-body{border-color:#1e66f5;color:#0a52e1}html.theme--catppuccin-latte .message.is-link{background-color:#ebf2fe}html.theme--catppuccin-latte .message.is-link .message-header{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .message.is-link .message-body{border-color:#1e66f5;color:#0a52e1}html.theme--catppuccin-latte .message.is-info{background-color:#edfcfc}html.theme--catppuccin-latte .message.is-info .message-header{background-color:#179299;color:#fff}html.theme--catppuccin-latte .message.is-info .message-body{border-color:#179299;color:#1cb2ba}html.theme--catppuccin-latte .message.is-success{background-color:#f1fbef}html.theme--catppuccin-latte .message.is-success .message-header{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .message.is-success .message-body{border-color:#40a02b;color:#40a12b}html.theme--catppuccin-latte .message.is-warning{background-color:#fdf6ed}html.theme--catppuccin-latte .message.is-warning .message-header{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .message.is-warning .message-body{border-color:#df8e1d;color:#9e6515}html.theme--catppuccin-latte .message.is-danger{background-color:#feecf0}html.theme--catppuccin-latte .message.is-danger .message-header{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .message.is-danger .message-body{border-color:#d20f39;color:#e9113f}html.theme--catppuccin-latte .message-header{align-items:center;background-color:#4c4f69;border-radius:.4em .4em 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-latte .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-latte .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .message-body{border-color:#acb0be;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#4c4f69;padding:1.25em 1.5em}html.theme--catppuccin-latte .message-body code,html.theme--catppuccin-latte .message-body pre{background-color:#fff}html.theme--catppuccin-latte .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-latte .modal.is-active{display:flex}html.theme--catppuccin-latte .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-latte .modal-content,html.theme--catppuccin-latte .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-latte .modal-content,html.theme--catppuccin-latte .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-latte .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-latte .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-latte .modal-card-head,html.theme--catppuccin-latte .modal-card-foot{align-items:center;background-color:#e6e9ef;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-latte .modal-card-head{border-bottom:1px solid #acb0be;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-latte .modal-card-title{color:#4c4f69;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-latte .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #acb0be}html.theme--catppuccin-latte .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-latte .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#eff1f5;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-latte .navbar{background-color:#1e66f5;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-latte .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-latte .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-latte .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-latte .navbar.is-dark,html.theme--catppuccin-latte .content kbd.navbar{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-burger,html.theme--catppuccin-latte .content kbd.navbar .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#ccd0da;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-latte .navbar.is-primary,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-burger,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5;color:#fff}}html.theme--catppuccin-latte .navbar.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5;color:#fff}}html.theme--catppuccin-latte .navbar.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#179299;color:#fff}}html.theme--catppuccin-latte .navbar.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#40a02b;color:#fff}}html.theme--catppuccin-latte .navbar.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#df8e1d;color:#fff}}html.theme--catppuccin-latte .navbar.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#d20f39;color:#fff}}html.theme--catppuccin-latte .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-latte .navbar.has-shadow{box-shadow:0 2px 0 0 #e6e9ef}html.theme--catppuccin-latte .navbar.is-fixed-bottom,html.theme--catppuccin-latte .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #e6e9ef}html.theme--catppuccin-latte .navbar.is-fixed-top{top:0}html.theme--catppuccin-latte html.has-navbar-fixed-top,html.theme--catppuccin-latte body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom,html.theme--catppuccin-latte body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-latte .navbar-brand,html.theme--catppuccin-latte .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-latte .navbar-brand a.navbar-item:focus,html.theme--catppuccin-latte .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-latte .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-latte .navbar-burger{color:#4c4f69;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-latte .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-latte .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-latte .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-latte .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-latte .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-latte .navbar-menu{display:none}html.theme--catppuccin-latte .navbar-item,html.theme--catppuccin-latte .navbar-link{color:#4c4f69;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-latte .navbar-item .icon:only-child,html.theme--catppuccin-latte .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-latte a.navbar-item,html.theme--catppuccin-latte .navbar-link{cursor:pointer}html.theme--catppuccin-latte a.navbar-item:focus,html.theme--catppuccin-latte a.navbar-item:focus-within,html.theme--catppuccin-latte a.navbar-item:hover,html.theme--catppuccin-latte a.navbar-item.is-active,html.theme--catppuccin-latte .navbar-link:focus,html.theme--catppuccin-latte .navbar-link:focus-within,html.theme--catppuccin-latte .navbar-link:hover,html.theme--catppuccin-latte .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}html.theme--catppuccin-latte .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .navbar-item img{max-height:1.75rem}html.theme--catppuccin-latte .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-latte .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-latte .navbar-item.is-tab:focus,html.theme--catppuccin-latte .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1e66f5}html.theme--catppuccin-latte .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1e66f5;border-bottom-style:solid;border-bottom-width:3px;color:#1e66f5;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-latte .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-latte .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-latte .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-latte .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .navbar>.container{display:block}html.theme--catppuccin-latte .navbar-brand .navbar-item,html.theme--catppuccin-latte .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-latte .navbar-link::after{display:none}html.theme--catppuccin-latte .navbar-menu{background-color:#1e66f5;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-latte .navbar-menu.is-active{display:block}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch,html.theme--catppuccin-latte .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-latte .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-latte .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-latte .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-latte html.has-navbar-fixed-top-touch,html.theme--catppuccin-latte body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-latte body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar,html.theme--catppuccin-latte .navbar-menu,html.theme--catppuccin-latte .navbar-start,html.theme--catppuccin-latte .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-latte .navbar{min-height:4rem}html.theme--catppuccin-latte .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-latte .navbar.is-spaced .navbar-start,html.theme--catppuccin-latte .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-latte .navbar.is-spaced a.navbar-item,html.theme--catppuccin-latte .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8c8fa1}html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}html.theme--catppuccin-latte .navbar-burger{display:none}html.theme--catppuccin-latte .navbar-item,html.theme--catppuccin-latte .navbar-link{align-items:center;display:flex}html.theme--catppuccin-latte .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-latte .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-latte .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-latte .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-latte .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-latte .navbar-dropdown{background-color:#1e66f5;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-latte .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-latte .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8c8fa1}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}.navbar.is-spaced html.theme--catppuccin-latte .navbar-dropdown,html.theme--catppuccin-latte .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-latte .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-latte .navbar-divider{display:block}html.theme--catppuccin-latte .navbar>.container .navbar-brand,html.theme--catppuccin-latte .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-latte .navbar>.container .navbar-menu,html.theme--catppuccin-latte .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-latte .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-latte .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-latte html.has-navbar-fixed-top-desktop,html.theme--catppuccin-latte body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-latte body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-latte html.has-spaced-navbar-fixed-top,html.theme--catppuccin-latte body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-latte html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-latte body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-latte a.navbar-item.is-active,html.theme--catppuccin-latte .navbar-link.is-active{color:#1e66f5}html.theme--catppuccin-latte a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-latte .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-latte .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-latte .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-latte .pagination.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-latte .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-latte .pagination.is-rounded .pagination-previous,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-latte .pagination.is-rounded .pagination-next,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-latte .pagination.is-rounded .pagination-link,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-latte .pagination,html.theme--catppuccin-latte .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link{border-color:#acb0be;color:#1e66f5;min-width:2.5em}html.theme--catppuccin-latte .pagination-previous:hover,html.theme--catppuccin-latte .pagination-next:hover,html.theme--catppuccin-latte .pagination-link:hover{border-color:#9ca0b0;color:#04a5e5}html.theme--catppuccin-latte .pagination-previous:focus,html.theme--catppuccin-latte .pagination-next:focus,html.theme--catppuccin-latte .pagination-link:focus{border-color:#9ca0b0}html.theme--catppuccin-latte .pagination-previous:active,html.theme--catppuccin-latte .pagination-next:active,html.theme--catppuccin-latte .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-latte .pagination-previous[disabled],html.theme--catppuccin-latte .pagination-previous.is-disabled,html.theme--catppuccin-latte .pagination-next[disabled],html.theme--catppuccin-latte .pagination-next.is-disabled,html.theme--catppuccin-latte .pagination-link[disabled],html.theme--catppuccin-latte .pagination-link.is-disabled{background-color:#acb0be;border-color:#acb0be;box-shadow:none;color:#616587;opacity:0.5}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-latte .pagination-link.is-current{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .pagination-ellipsis{color:#9ca0b0;pointer-events:none}html.theme--catppuccin-latte .pagination-list{flex-wrap:wrap}html.theme--catppuccin-latte .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-latte .pagination{flex-wrap:wrap}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-latte .pagination-previous{order:2}html.theme--catppuccin-latte .pagination-next{order:3}html.theme--catppuccin-latte .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-latte .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-latte .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-latte .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-latte .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-latte .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-latte .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-latte .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-latte .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-latte .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-latte .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-latte .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-latte .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-latte .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-latte .panel.is-dark .panel-heading,html.theme--catppuccin-latte .content kbd.panel .panel-heading{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-latte .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#ccd0da}html.theme--catppuccin-latte .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-latte .content kbd.panel .panel-block.is-active .panel-icon{color:#ccd0da}html.theme--catppuccin-latte .panel.is-primary .panel-heading,html.theme--catppuccin-latte details.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-latte details.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#1e66f5}html.theme--catppuccin-latte .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-latte details.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel.is-link .panel-heading{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1e66f5}html.theme--catppuccin-latte .panel.is-link .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel.is-info .panel-heading{background-color:#179299;color:#fff}html.theme--catppuccin-latte .panel.is-info .panel-tabs a.is-active{border-bottom-color:#179299}html.theme--catppuccin-latte .panel.is-info .panel-block.is-active .panel-icon{color:#179299}html.theme--catppuccin-latte .panel.is-success .panel-heading{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .panel.is-success .panel-tabs a.is-active{border-bottom-color:#40a02b}html.theme--catppuccin-latte .panel.is-success .panel-block.is-active .panel-icon{color:#40a02b}html.theme--catppuccin-latte .panel.is-warning .panel-heading{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#df8e1d}html.theme--catppuccin-latte .panel.is-warning .panel-block.is-active .panel-icon{color:#df8e1d}html.theme--catppuccin-latte .panel.is-danger .panel-heading{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#d20f39}html.theme--catppuccin-latte .panel.is-danger .panel-block.is-active .panel-icon{color:#d20f39}html.theme--catppuccin-latte .panel-tabs:not(:last-child),html.theme--catppuccin-latte .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-latte .panel-heading{background-color:#bcc0cc;border-radius:8px 8px 0 0;color:#41445a;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-latte .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-latte .panel-tabs a{border-bottom:1px solid #acb0be;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-latte .panel-tabs a.is-active{border-bottom-color:#bcc0cc;color:#0b57ef}html.theme--catppuccin-latte .panel-list a{color:#4c4f69}html.theme--catppuccin-latte .panel-list a:hover{color:#1e66f5}html.theme--catppuccin-latte .panel-block{align-items:center;color:#41445a;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-latte .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-latte .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-latte .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-latte .panel-block.is-active{border-left-color:#1e66f5;color:#0b57ef}html.theme--catppuccin-latte .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-latte a.panel-block,html.theme--catppuccin-latte label.panel-block{cursor:pointer}html.theme--catppuccin-latte a.panel-block:hover,html.theme--catppuccin-latte label.panel-block:hover{background-color:#e6e9ef}html.theme--catppuccin-latte .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#616587;margin-right:.75em}html.theme--catppuccin-latte .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-latte .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-latte .tabs a{align-items:center;border-bottom-color:#acb0be;border-bottom-style:solid;border-bottom-width:1px;color:#4c4f69;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-latte .tabs a:hover{border-bottom-color:#41445a;color:#41445a}html.theme--catppuccin-latte .tabs li{display:block}html.theme--catppuccin-latte .tabs li.is-active a{border-bottom-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .tabs ul{align-items:center;border-bottom-color:#acb0be;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-latte .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-latte .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-latte .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-latte .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-latte .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-latte .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-latte .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-latte .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-latte .tabs.is-boxed a:hover{background-color:#e6e9ef;border-bottom-color:#acb0be}html.theme--catppuccin-latte .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#acb0be;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-latte .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .tabs.is-toggle a{border-color:#acb0be;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-latte .tabs.is-toggle a:hover{background-color:#e6e9ef;border-color:#9ca0b0;z-index:2}html.theme--catppuccin-latte .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-latte .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-latte .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-latte .tabs.is-toggle li.is-active a{background-color:#1e66f5;border-color:#1e66f5;color:#fff;z-index:1}html.theme--catppuccin-latte .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-latte .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-latte .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-latte .tabs.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-latte .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-latte .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-latte .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-latte .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-latte .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-latte .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-latte .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-latte .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-latte .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-latte .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .column.is-narrow,html.theme--catppuccin-latte .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full,html.theme--catppuccin-latte .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters,html.theme--catppuccin-latte .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds,html.theme--catppuccin-latte .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half,html.theme--catppuccin-latte .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third,html.theme--catppuccin-latte .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter,html.theme--catppuccin-latte .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth,html.theme--catppuccin-latte .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths,html.theme--catppuccin-latte .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths,html.theme--catppuccin-latte .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths,html.theme--catppuccin-latte .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters,html.theme--catppuccin-latte .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds,html.theme--catppuccin-latte .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half,html.theme--catppuccin-latte .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third,html.theme--catppuccin-latte .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter,html.theme--catppuccin-latte .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth,html.theme--catppuccin-latte .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths,html.theme--catppuccin-latte .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths,html.theme--catppuccin-latte .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths,html.theme--catppuccin-latte .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-latte .column.is-0,html.theme--catppuccin-latte .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0,html.theme--catppuccin-latte .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-latte .column.is-1,html.theme--catppuccin-latte .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1,html.theme--catppuccin-latte .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2,html.theme--catppuccin-latte .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2,html.theme--catppuccin-latte .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3,html.theme--catppuccin-latte .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3,html.theme--catppuccin-latte .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-latte .column.is-4,html.theme--catppuccin-latte .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4,html.theme--catppuccin-latte .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5,html.theme--catppuccin-latte .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5,html.theme--catppuccin-latte .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6,html.theme--catppuccin-latte .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6,html.theme--catppuccin-latte .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-latte .column.is-7,html.theme--catppuccin-latte .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7,html.theme--catppuccin-latte .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8,html.theme--catppuccin-latte .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8,html.theme--catppuccin-latte .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9,html.theme--catppuccin-latte .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9,html.theme--catppuccin-latte .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-latte .column.is-10,html.theme--catppuccin-latte .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10,html.theme--catppuccin-latte .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11,html.theme--catppuccin-latte .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11,html.theme--catppuccin-latte .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12,html.theme--catppuccin-latte .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12,html.theme--catppuccin-latte .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-latte .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-latte .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-latte .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-latte .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-latte .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-latte .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-latte .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-latte .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-latte .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-latte .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-latte .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-latte .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-latte .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-latte .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-latte .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-latte .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-latte .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-latte .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-latte .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-latte .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-latte .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-latte .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-latte .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-latte .columns.is-centered{justify-content:center}html.theme--catppuccin-latte .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-latte .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-latte .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-latte .columns.is-mobile{display:flex}html.theme--catppuccin-latte .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-latte .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-desktop{display:flex}}html.theme--catppuccin-latte .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-latte .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-latte .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-latte .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-latte .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-latte .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-latte .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-latte .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-latte .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-latte .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-latte .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-latte .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-latte .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-latte .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-latte .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-latte .tile.is-child{margin:0 !important}html.theme--catppuccin-latte .tile.is-parent{padding:.75rem}html.theme--catppuccin-latte .tile.is-vertical{flex-direction:column}html.theme--catppuccin-latte .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .tile:not(.is-child){display:flex}html.theme--catppuccin-latte .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-latte .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-latte .tile.is-3{flex:none;width:25%}html.theme--catppuccin-latte .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-latte .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-latte .tile.is-6{flex:none;width:50%}html.theme--catppuccin-latte .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-latte .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-latte .tile.is-9{flex:none;width:75%}html.theme--catppuccin-latte .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-latte .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-latte .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-latte .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-latte .hero .navbar{background:none}html.theme--catppuccin-latte .hero .tabs ul{border-bottom:none}html.theme--catppuccin-latte .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-white strong{color:inherit}html.theme--catppuccin-latte .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-latte .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-latte .hero.is-white .navbar-item,html.theme--catppuccin-latte .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-latte .hero.is-white a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-white .navbar-link:hover,html.theme--catppuccin-latte .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-latte .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-latte .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-black strong{color:inherit}html.theme--catppuccin-latte .hero.is-black .title{color:#fff}html.theme--catppuccin-latte .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-latte .hero.is-black .navbar-item,html.theme--catppuccin-latte .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-black a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-black .navbar-link:hover,html.theme--catppuccin-latte .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-latte .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-light strong{color:inherit}html.theme--catppuccin-latte .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-latte .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-latte .hero.is-light .navbar-item,html.theme--catppuccin-latte .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-light .navbar-link:hover,html.theme--catppuccin-latte .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-latte .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-latte .hero.is-dark,html.theme--catppuccin-latte .content kbd.hero{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-dark strong,html.theme--catppuccin-latte .content kbd.hero strong{color:inherit}html.theme--catppuccin-latte .hero.is-dark .title,html.theme--catppuccin-latte .content kbd.hero .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .subtitle,html.theme--catppuccin-latte .content kbd.hero .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-latte .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-latte .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-dark .subtitle strong,html.theme--catppuccin-latte .content kbd.hero .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-dark .navbar-menu,html.theme--catppuccin-latte .content kbd.hero .navbar-menu{background-color:#ccd0da}}html.theme--catppuccin-latte .hero.is-dark .navbar-item,html.theme--catppuccin-latte .content kbd.hero .navbar-item,html.theme--catppuccin-latte .hero.is-dark .navbar-link,html.theme--catppuccin-latte .content kbd.hero .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-dark .navbar-link:hover,html.theme--catppuccin-latte .content kbd.hero .navbar-link:hover,html.theme--catppuccin-latte .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.hero .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .tabs a,html.theme--catppuccin-latte .content kbd.hero .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-latte .hero.is-dark .tabs a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs li.is-active a{color:#ccd0da !important;opacity:1}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .hero.is-dark.is-bold,html.theme--catppuccin-latte .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #a7b8cc 0%, #ccd0da 71%, #d9dbe6 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-latte .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #a7b8cc 0%, #ccd0da 71%, #d9dbe6 100%)}}html.theme--catppuccin-latte .hero.is-primary,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-primary strong,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-latte .hero.is-primary .title,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-latte .hero.is-primary .subtitle,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-primary .subtitle strong,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-primary .navbar-menu,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#1e66f5}}html.theme--catppuccin-latte .hero.is-primary .navbar-item,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-latte .hero.is-primary .navbar-link,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-primary .navbar-link:hover,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-latte .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .hero.is-primary .tabs a,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-primary .tabs a:hover,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#1e66f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .hero.is-primary.is-bold,html.theme--catppuccin-latte details.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-latte details.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}}html.theme--catppuccin-latte .hero.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-link strong{color:inherit}html.theme--catppuccin-latte .hero.is-link .title{color:#fff}html.theme--catppuccin-latte .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-link .navbar-menu{background-color:#1e66f5}}html.theme--catppuccin-latte .hero.is-link .navbar-item,html.theme--catppuccin-latte .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-link a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-link .navbar-link:hover,html.theme--catppuccin-latte .hero.is-link .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-link .tabs li.is-active a{color:#1e66f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}}html.theme--catppuccin-latte .hero.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-info strong{color:inherit}html.theme--catppuccin-latte .hero.is-info .title{color:#fff}html.theme--catppuccin-latte .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-info .navbar-menu{background-color:#179299}}html.theme--catppuccin-latte .hero.is-info .navbar-item,html.theme--catppuccin-latte .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-info a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-info .navbar-link:hover,html.theme--catppuccin-latte .hero.is-info .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-info .tabs li.is-active a{color:#179299 !important;opacity:1}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#179299}html.theme--catppuccin-latte .hero.is-info.is-bold{background-image:linear-gradient(141deg, #0a7367 0%, #179299 71%, #1591b4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0a7367 0%, #179299 71%, #1591b4 100%)}}html.theme--catppuccin-latte .hero.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-success strong{color:inherit}html.theme--catppuccin-latte .hero.is-success .title{color:#fff}html.theme--catppuccin-latte .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-success .navbar-menu{background-color:#40a02b}}html.theme--catppuccin-latte .hero.is-success .navbar-item,html.theme--catppuccin-latte .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-success a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-success .navbar-link:hover,html.theme--catppuccin-latte .hero.is-success .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-success .tabs li.is-active a{color:#40a02b !important;opacity:1}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#40a02b}html.theme--catppuccin-latte .hero.is-success.is-bold{background-image:linear-gradient(141deg, #3c7f19 0%, #40a02b 71%, #2dba2b 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #3c7f19 0%, #40a02b 71%, #2dba2b 100%)}}html.theme--catppuccin-latte .hero.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-warning strong{color:inherit}html.theme--catppuccin-latte .hero.is-warning .title{color:#fff}html.theme--catppuccin-latte .hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-warning .navbar-menu{background-color:#df8e1d}}html.theme--catppuccin-latte .hero.is-warning .navbar-item,html.theme--catppuccin-latte .hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-warning .navbar-link:hover,html.theme--catppuccin-latte .hero.is-warning .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .hero.is-warning .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-warning .tabs li.is-active a{color:#df8e1d !important;opacity:1}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #bc560d 0%, #df8e1d 71%, #eaba2b 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #bc560d 0%, #df8e1d 71%, #eaba2b 100%)}}html.theme--catppuccin-latte .hero.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-danger strong{color:inherit}html.theme--catppuccin-latte .hero.is-danger .title{color:#fff}html.theme--catppuccin-latte .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-danger .navbar-menu{background-color:#d20f39}}html.theme--catppuccin-latte .hero.is-danger .navbar-item,html.theme--catppuccin-latte .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-danger .navbar-link:hover,html.theme--catppuccin-latte .hero.is-danger .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-danger .tabs li.is-active a{color:#d20f39 !important;opacity:1}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#d20f39}html.theme--catppuccin-latte .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ab0343 0%, #d20f39 71%, #f00a16 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ab0343 0%, #d20f39 71%, #f00a16 100%)}}html.theme--catppuccin-latte .hero.is-small .hero-body,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-latte .hero.is-halfheight .hero-body,html.theme--catppuccin-latte .hero.is-fullheight .hero-body,html.theme--catppuccin-latte .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-latte .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-latte .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-latte .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-latte .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-latte .hero-video{overflow:hidden}html.theme--catppuccin-latte .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-latte .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero-video{display:none}}html.theme--catppuccin-latte .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero-buttons .button{display:flex}html.theme--catppuccin-latte .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-latte .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-latte .hero-head,html.theme--catppuccin-latte .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero-body{padding:3rem 3rem}}html.theme--catppuccin-latte .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .section{padding:3rem 3rem}html.theme--catppuccin-latte .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-latte .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-latte .footer{background-color:#e6e9ef;padding:3rem 1.5rem 6rem}html.theme--catppuccin-latte h1 .docs-heading-anchor,html.theme--catppuccin-latte h1 .docs-heading-anchor:hover,html.theme--catppuccin-latte h1 .docs-heading-anchor:visited,html.theme--catppuccin-latte h2 .docs-heading-anchor,html.theme--catppuccin-latte h2 .docs-heading-anchor:hover,html.theme--catppuccin-latte h2 .docs-heading-anchor:visited,html.theme--catppuccin-latte h3 .docs-heading-anchor,html.theme--catppuccin-latte h3 .docs-heading-anchor:hover,html.theme--catppuccin-latte h3 .docs-heading-anchor:visited,html.theme--catppuccin-latte h4 .docs-heading-anchor,html.theme--catppuccin-latte h4 .docs-heading-anchor:hover,html.theme--catppuccin-latte h4 .docs-heading-anchor:visited,html.theme--catppuccin-latte h5 .docs-heading-anchor,html.theme--catppuccin-latte h5 .docs-heading-anchor:hover,html.theme--catppuccin-latte h5 .docs-heading-anchor:visited,html.theme--catppuccin-latte h6 .docs-heading-anchor,html.theme--catppuccin-latte h6 .docs-heading-anchor:hover,html.theme--catppuccin-latte h6 .docs-heading-anchor:visited{color:#4c4f69}html.theme--catppuccin-latte h1 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h2 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h3 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h4 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h5 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-latte h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-latte h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-latte .docs-dark-only{display:none !important}html.theme--catppuccin-latte pre{position:relative;overflow:hidden}html.theme--catppuccin-latte pre code,html.theme--catppuccin-latte pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-latte pre code:first-of-type,html.theme--catppuccin-latte pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-latte pre code:last-of-type,html.theme--catppuccin-latte pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-latte pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#4c4f69;cursor:pointer;text-align:center}html.theme--catppuccin-latte pre .copy-button:focus,html.theme--catppuccin-latte pre .copy-button:hover{opacity:1;background:rgba(76,79,105,0.1);color:#1e66f5}html.theme--catppuccin-latte pre .copy-button.success{color:#40a02b;opacity:1}html.theme--catppuccin-latte pre .copy-button.error{color:#d20f39;opacity:1}html.theme--catppuccin-latte pre:hover .copy-button{opacity:1}html.theme--catppuccin-latte .link-icon:hover{color:#1e66f5}html.theme--catppuccin-latte .admonition{background-color:#e6e9ef;border-style:solid;border-width:2px;border-color:#5c5f77;border-radius:4px;font-size:1rem}html.theme--catppuccin-latte .admonition strong{color:currentColor}html.theme--catppuccin-latte .admonition.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-latte .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-latte .admonition.is-default{background-color:#e6e9ef;border-color:#5c5f77}html.theme--catppuccin-latte .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#5c5f77}html.theme--catppuccin-latte .admonition.is-default>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-info{background-color:#e6e9ef;border-color:#179299}html.theme--catppuccin-latte .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#179299}html.theme--catppuccin-latte .admonition.is-info>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-success{background-color:#e6e9ef;border-color:#40a02b}html.theme--catppuccin-latte .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#40a02b}html.theme--catppuccin-latte .admonition.is-success>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-warning{background-color:#e6e9ef;border-color:#df8e1d}html.theme--catppuccin-latte .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#df8e1d}html.theme--catppuccin-latte .admonition.is-warning>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-danger{background-color:#e6e9ef;border-color:#d20f39}html.theme--catppuccin-latte .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#d20f39}html.theme--catppuccin-latte .admonition.is-danger>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-compat{background-color:#e6e9ef;border-color:#04a5e5}html.theme--catppuccin-latte .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#04a5e5}html.theme--catppuccin-latte .admonition.is-compat>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-todo{background-color:#e6e9ef;border-color:#8839ef}html.theme--catppuccin-latte .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#8839ef}html.theme--catppuccin-latte .admonition.is-todo>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition-header{color:#5c5f77;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-latte .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-latte .admonition-header .admonition-anchor{opacity:0;margin-left:0.5em;font-size:0.75em;color:inherit;text-decoration:none;transition:opacity 0.2s ease-in-out}html.theme--catppuccin-latte .admonition-header .admonition-anchor:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-latte .admonition-header .admonition-anchor:hover{opacity:1 !important;text-decoration:none}html.theme--catppuccin-latte .admonition-header:hover .admonition-anchor{opacity:0.8}html.theme--catppuccin-latte details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-latte details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-latte details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-latte .admonition-body{color:#4c4f69;padding:0.5rem .75rem}html.theme--catppuccin-latte .admonition-body pre{background-color:#e6e9ef}html.theme--catppuccin-latte .admonition-body code{background-color:#e6e9ef}html.theme--catppuccin-latte details.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #acb0be;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-latte details.docstring>summary{list-style-type:none;align-items:stretch;padding:0.5rem .75rem;background-color:#e6e9ef;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #acb0be;overflow:auto}html.theme--catppuccin-latte details.docstring>summary code{background-color:transparent}html.theme--catppuccin-latte details.docstring>summary .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-latte details.docstring>summary .docstring-binding{margin-right:0.3em}html.theme--catppuccin-latte details.docstring>summary .docstring-category{margin-left:0.3em}html.theme--catppuccin-latte details.docstring>summary::before{content:'\f054';font-family:"Font Awesome 6 Free";font-weight:900;min-width:1.1rem;color:#2E63BD;display:inline-block}html.theme--catppuccin-latte details.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #acb0be}html.theme--catppuccin-latte details.docstring>section:last-child{border-bottom:none}html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-latte details.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-latte details.docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-latte details.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-latte details.docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-latte details.docstring[open]>summary::before{content:"\f078"}html.theme--catppuccin-latte .documenter-example-output{background-color:#eff1f5}html.theme--catppuccin-latte .warning-overlay-base,html.theme--catppuccin-latte .dev-warning-overlay,html.theme--catppuccin-latte .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-latte .warning-overlay-base .outdated-warning-closer,html.theme--catppuccin-latte .dev-warning-overlay .outdated-warning-closer,html.theme--catppuccin-latte .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-latte .warning-overlay-base a,html.theme--catppuccin-latte .dev-warning-overlay a,html.theme--catppuccin-latte .outdated-warning-overlay a{color:#1e66f5}html.theme--catppuccin-latte .warning-overlay-base a:hover,html.theme--catppuccin-latte .dev-warning-overlay a:hover,html.theme--catppuccin-latte .outdated-warning-overlay a:hover{color:#04a5e5}html.theme--catppuccin-latte .outdated-warning-overlay{background-color:#e6e9ef;color:#4c4f69;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-latte .dev-warning-overlay{background-color:#e6e9ef;color:#4c4f69;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-latte .footnote-reference{position:relative;display:inline-block}html.theme--catppuccin-latte .footnote-preview{display:none;position:absolute;z-index:1000;max-width:300px;width:max-content;background-color:#eff1f5;border:1px solid #04a5e5;padding:10px;border-radius:5px;top:calc(100% + 10px);left:50%;transform:translateX(-50%);box-sizing:border-box;--arrow-left: 50%}html.theme--catppuccin-latte .footnote-preview::before{content:"";position:absolute;top:-10px;left:var(--arrow-left);transform:translateX(-50%);border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #04a5e5}html.theme--catppuccin-latte .content pre{border:2px solid #acb0be;border-radius:4px}html.theme--catppuccin-latte .content code{font-weight:inherit}html.theme--catppuccin-latte .content a code{color:#1e66f5}html.theme--catppuccin-latte .content a:hover code{color:#04a5e5}html.theme--catppuccin-latte .content h1 code,html.theme--catppuccin-latte .content h2 code,html.theme--catppuccin-latte .content h3 code,html.theme--catppuccin-latte .content h4 code,html.theme--catppuccin-latte .content h5 code,html.theme--catppuccin-latte .content h6 code{color:#4c4f69}html.theme--catppuccin-latte .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-latte .content blockquote>ul:first-child,html.theme--catppuccin-latte .content blockquote>ol:first-child,html.theme--catppuccin-latte .content .admonition-body>ul:first-child,html.theme--catppuccin-latte .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-latte pre,html.theme--catppuccin-latte code{font-variant-ligatures:no-contextual}html.theme--catppuccin-latte .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-latte .breadcrumb a.is-disabled,html.theme--catppuccin-latte .breadcrumb a.is-disabled:hover{color:#41445a}html.theme--catppuccin-latte .hljs{background:initial !important}html.theme--catppuccin-latte .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-latte .katex-display,html.theme--catppuccin-latte mjx-container,html.theme--catppuccin-latte .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-latte html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-latte li.no-marker{list-style:none}html.theme--catppuccin-latte #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-latte #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main{width:100%}html.theme--catppuccin-latte #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-latte #documenter .docs-main>header,html.theme--catppuccin-latte #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar{background-color:#eff1f5;border-bottom:1px solid #acb0be;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow:hidden}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-latte #documenter .docs-main section.footnotes{border-top:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-latte #documenter .docs-main section.footnotes li details.docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-latte .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-latte #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #acb0be;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-latte #documenter .docs-sidebar{display:flex;flex-direction:column;color:#4c4f69;background-color:#e6e9ef;border-right:1px solid #acb0be;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-latte #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name a:hover{color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #acb0be;display:none;padding:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #acb0be;padding-bottom:1.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#4c4f69;background:#e6e9ef}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#4c4f69;background-color:#f2f4f7}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #acb0be;border-bottom:1px solid #acb0be;background-color:#dce0e8}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#dce0e8;color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#f2f4f7;color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"โšฌ";margin-right:0.4em}html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-latte #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#fff}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#fff}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#fff}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#fff}}html.theme--catppuccin-latte kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-latte .search-min-width-50{min-width:50%}html.theme--catppuccin-latte .search-min-height-100{min-height:100%}html.theme--catppuccin-latte .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-latte .search-result-link{border-radius:0.7em;transition:all 300ms;border:1px solid transparent}html.theme--catppuccin-latte .search-result-link:hover,html.theme--catppuccin-latte .search-result-link:focus{background-color:rgba(0,128,128,0.1);outline:none;border-color:#179299}html.theme--catppuccin-latte .search-result-link .property-search-result-badge,html.theme--catppuccin-latte .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-latte .property-search-result-badge,html.theme--catppuccin-latte .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-latte .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:hover .search-filter,html.theme--catppuccin-latte .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-latte .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-latte .search-filter:hover,html.theme--catppuccin-latte .search-filter:focus{color:#333}html.theme--catppuccin-latte .search-filter-selected{color:#ccd0da;background-color:#7287fd}html.theme--catppuccin-latte .search-filter-selected:hover,html.theme--catppuccin-latte .search-filter-selected:focus{color:#ccd0da}html.theme--catppuccin-latte .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-latte .search-divider{border-bottom:1px solid #acb0be}html.theme--catppuccin-latte .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-latte .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-latte .w-100{width:100%}html.theme--catppuccin-latte .gap-2{gap:0.5rem}html.theme--catppuccin-latte .gap-4{gap:1rem}html.theme--catppuccin-latte .gap-8{gap:2rem}html.theme--catppuccin-latte{background-color:#eff1f5;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-latte a{transition:all 200ms ease}html.theme--catppuccin-latte .label{color:#4c4f69}html.theme--catppuccin-latte .button,html.theme--catppuccin-latte .control.has-icons-left .icon,html.theme--catppuccin-latte .control.has-icons-right .icon,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .select,html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea{height:2.5em;color:#4c4f69}html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#4c4f69}html.theme--catppuccin-latte .select:after,html.theme--catppuccin-latte .select select{border-width:1px}html.theme--catppuccin-latte .menu-list a{transition:all 300ms ease}html.theme--catppuccin-latte .modal-card-foot,html.theme--catppuccin-latte .modal-card-head{border-color:#acb0be}html.theme--catppuccin-latte .navbar{border-radius:.4em}html.theme--catppuccin-latte .navbar.is-transparent{background:none}html.theme--catppuccin-latte .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .navbar .navbar-menu{background-color:#1e66f5;border-radius:0 0 .4em .4em}}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){color:#ccd0da}html.theme--catppuccin-latte .tag.is-link:not(body),html.theme--catppuccin-latte details.docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-latte .content kbd.is-link:not(body){color:#ccd0da}html.theme--catppuccin-latte .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-latte .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-latte .ansi span.sgr3{font-style:italic}html.theme--catppuccin-latte .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-latte .ansi span.sgr7{color:#eff1f5;background-color:#4c4f69}html.theme--catppuccin-latte .ansi span.sgr8{color:transparent}html.theme--catppuccin-latte .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-latte .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-latte .ansi span.sgr30{color:#5c5f77}html.theme--catppuccin-latte .ansi span.sgr31{color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr32{color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr33{color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr34{color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr35{color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr36{color:#179299}html.theme--catppuccin-latte .ansi span.sgr37{color:#acb0be}html.theme--catppuccin-latte .ansi span.sgr40{background-color:#5c5f77}html.theme--catppuccin-latte .ansi span.sgr41{background-color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr42{background-color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr43{background-color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr44{background-color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr45{background-color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr46{background-color:#179299}html.theme--catppuccin-latte .ansi span.sgr47{background-color:#acb0be}html.theme--catppuccin-latte .ansi span.sgr90{color:#6c6f85}html.theme--catppuccin-latte .ansi span.sgr91{color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr92{color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr93{color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr94{color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr95{color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr96{color:#179299}html.theme--catppuccin-latte .ansi span.sgr97{color:#bcc0cc}html.theme--catppuccin-latte .ansi span.sgr100{background-color:#6c6f85}html.theme--catppuccin-latte .ansi span.sgr101{background-color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr102{background-color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr103{background-color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr104{background-color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr105{background-color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr106{background-color:#179299}html.theme--catppuccin-latte .ansi span.sgr107{background-color:#bcc0cc}html.theme--catppuccin-latte code.language-julia-repl>span.hljs-meta{color:#40a02b;font-weight:bolder}html.theme--catppuccin-latte code .hljs{color:#4c4f69;background:#eff1f5}html.theme--catppuccin-latte code .hljs-keyword{color:#8839ef}html.theme--catppuccin-latte code .hljs-built_in{color:#d20f39}html.theme--catppuccin-latte code .hljs-type{color:#df8e1d}html.theme--catppuccin-latte code .hljs-literal{color:#fe640b}html.theme--catppuccin-latte code .hljs-number{color:#fe640b}html.theme--catppuccin-latte code .hljs-operator{color:#179299}html.theme--catppuccin-latte code .hljs-punctuation{color:#5c5f77}html.theme--catppuccin-latte code .hljs-property{color:#179299}html.theme--catppuccin-latte code .hljs-regexp{color:#ea76cb}html.theme--catppuccin-latte code .hljs-string{color:#40a02b}html.theme--catppuccin-latte code .hljs-char.escape_{color:#40a02b}html.theme--catppuccin-latte code .hljs-subst{color:#6c6f85}html.theme--catppuccin-latte code .hljs-symbol{color:#dd7878}html.theme--catppuccin-latte code .hljs-variable{color:#8839ef}html.theme--catppuccin-latte code .hljs-variable.language_{color:#8839ef}html.theme--catppuccin-latte code .hljs-variable.constant_{color:#fe640b}html.theme--catppuccin-latte code .hljs-title{color:#1e66f5}html.theme--catppuccin-latte code .hljs-title.class_{color:#df8e1d}html.theme--catppuccin-latte code .hljs-title.function_{color:#1e66f5}html.theme--catppuccin-latte code .hljs-params{color:#4c4f69}html.theme--catppuccin-latte code .hljs-comment{color:#acb0be}html.theme--catppuccin-latte code .hljs-doctag{color:#d20f39}html.theme--catppuccin-latte code .hljs-meta{color:#fe640b}html.theme--catppuccin-latte code .hljs-section{color:#1e66f5}html.theme--catppuccin-latte code .hljs-tag{color:#6c6f85}html.theme--catppuccin-latte code .hljs-name{color:#8839ef}html.theme--catppuccin-latte code .hljs-attr{color:#1e66f5}html.theme--catppuccin-latte code .hljs-attribute{color:#40a02b}html.theme--catppuccin-latte code .hljs-bullet{color:#179299}html.theme--catppuccin-latte code .hljs-code{color:#40a02b}html.theme--catppuccin-latte code .hljs-emphasis{color:#d20f39;font-style:italic}html.theme--catppuccin-latte code .hljs-strong{color:#d20f39;font-weight:bold}html.theme--catppuccin-latte code .hljs-formula{color:#179299}html.theme--catppuccin-latte code .hljs-link{color:#209fb5;font-style:italic}html.theme--catppuccin-latte code .hljs-quote{color:#40a02b;font-style:italic}html.theme--catppuccin-latte code .hljs-selector-tag{color:#df8e1d}html.theme--catppuccin-latte code .hljs-selector-id{color:#1e66f5}html.theme--catppuccin-latte code .hljs-selector-class{color:#179299}html.theme--catppuccin-latte code .hljs-selector-attr{color:#8839ef}html.theme--catppuccin-latte code .hljs-selector-pseudo{color:#179299}html.theme--catppuccin-latte code .hljs-template-tag{color:#dd7878}html.theme--catppuccin-latte code .hljs-template-variable{color:#dd7878}html.theme--catppuccin-latte code .hljs-addition{color:#40a02b;background:rgba(166,227,161,0.15)}html.theme--catppuccin-latte code .hljs-deletion{color:#d20f39;background:rgba(243,139,168,0.15)}html.theme--catppuccin-latte .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover,html.theme--catppuccin-latte .search-result-link:focus{background-color:#ccd0da}html.theme--catppuccin-latte .search-result-link .property-search-result-badge,html.theme--catppuccin-latte .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:hover .search-filter,html.theme--catppuccin-latte .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:focus .search-filter{color:#ccd0da !important;background-color:#7287fd !important}html.theme--catppuccin-latte .search-result-title{color:#4c4f69}html.theme--catppuccin-latte .search-result-highlight{background-color:#d20f39;color:#e6e9ef}html.theme--catppuccin-latte .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-latte .w-100{width:100%}html.theme--catppuccin-latte .gap-2{gap:0.5rem}html.theme--catppuccin-latte .gap-4{gap:1rem} diff --git a/save/docs/build/assets/themes/catppuccin-macchiato.css b/save/docs/build/assets/themes/catppuccin-macchiato.css new file mode 100644 index 00000000..6d461ed0 --- /dev/null +++ b/save/docs/build/assets/themes/catppuccin-macchiato.css @@ -0,0 +1 @@ +๏ปฟhtml.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato .file-name,html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-macchiato .pagination-previous:focus,html.theme--catppuccin-macchiato .pagination-next:focus,html.theme--catppuccin-macchiato .pagination-link:focus,html.theme--catppuccin-macchiato .pagination-ellipsis:focus,html.theme--catppuccin-macchiato .file-cta:focus,html.theme--catppuccin-macchiato .file-name:focus,html.theme--catppuccin-macchiato .select select:focus,html.theme--catppuccin-macchiato .textarea:focus,html.theme--catppuccin-macchiato .input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-macchiato .button:focus,html.theme--catppuccin-macchiato .is-focused.pagination-previous,html.theme--catppuccin-macchiato .is-focused.pagination-next,html.theme--catppuccin-macchiato .is-focused.pagination-link,html.theme--catppuccin-macchiato .is-focused.pagination-ellipsis,html.theme--catppuccin-macchiato .is-focused.file-cta,html.theme--catppuccin-macchiato .is-focused.file-name,html.theme--catppuccin-macchiato .select select.is-focused,html.theme--catppuccin-macchiato .is-focused.textarea,html.theme--catppuccin-macchiato .is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-focused.button,html.theme--catppuccin-macchiato .pagination-previous:active,html.theme--catppuccin-macchiato .pagination-next:active,html.theme--catppuccin-macchiato .pagination-link:active,html.theme--catppuccin-macchiato .pagination-ellipsis:active,html.theme--catppuccin-macchiato .file-cta:active,html.theme--catppuccin-macchiato .file-name:active,html.theme--catppuccin-macchiato .select select:active,html.theme--catppuccin-macchiato .textarea:active,html.theme--catppuccin-macchiato .input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-macchiato .button:active,html.theme--catppuccin-macchiato .is-active.pagination-previous,html.theme--catppuccin-macchiato .is-active.pagination-next,html.theme--catppuccin-macchiato .is-active.pagination-link,html.theme--catppuccin-macchiato .is-active.pagination-ellipsis,html.theme--catppuccin-macchiato .is-active.file-cta,html.theme--catppuccin-macchiato .is-active.file-name,html.theme--catppuccin-macchiato .select select.is-active,html.theme--catppuccin-macchiato .is-active.textarea,html.theme--catppuccin-macchiato .is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .is-active.button{outline:none}html.theme--catppuccin-macchiato .pagination-previous[disabled],html.theme--catppuccin-macchiato .pagination-next[disabled],html.theme--catppuccin-macchiato .pagination-link[disabled],html.theme--catppuccin-macchiato .pagination-ellipsis[disabled],html.theme--catppuccin-macchiato .file-cta[disabled],html.theme--catppuccin-macchiato .file-name[disabled],html.theme--catppuccin-macchiato .select select[disabled],html.theme--catppuccin-macchiato .textarea[disabled],html.theme--catppuccin-macchiato .input[disabled],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-macchiato .button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-macchiato .file-name,html.theme--catppuccin-macchiato fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-macchiato .select select,fieldset[disabled] html.theme--catppuccin-macchiato .textarea,fieldset[disabled] html.theme--catppuccin-macchiato .input,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato fieldset[disabled] .select select,html.theme--catppuccin-macchiato .select fieldset[disabled] select,html.theme--catppuccin-macchiato fieldset[disabled] .textarea,html.theme--catppuccin-macchiato fieldset[disabled] .input,html.theme--catppuccin-macchiato fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-macchiato .button,html.theme--catppuccin-macchiato fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-macchiato .tabs,html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .breadcrumb,html.theme--catppuccin-macchiato .file,html.theme--catppuccin-macchiato .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-macchiato .admonition:not(:last-child),html.theme--catppuccin-macchiato .tabs:not(:last-child),html.theme--catppuccin-macchiato .pagination:not(:last-child),html.theme--catppuccin-macchiato .message:not(:last-child),html.theme--catppuccin-macchiato .level:not(:last-child),html.theme--catppuccin-macchiato .breadcrumb:not(:last-child),html.theme--catppuccin-macchiato .block:not(:last-child),html.theme--catppuccin-macchiato .title:not(:last-child),html.theme--catppuccin-macchiato .subtitle:not(:last-child),html.theme--catppuccin-macchiato .table-container:not(:last-child),html.theme--catppuccin-macchiato .table:not(:last-child),html.theme--catppuccin-macchiato .progress:not(:last-child),html.theme--catppuccin-macchiato .notification:not(:last-child),html.theme--catppuccin-macchiato .content:not(:last-child),html.theme--catppuccin-macchiato .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .modal-close,html.theme--catppuccin-macchiato .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-macchiato .modal-close::before,html.theme--catppuccin-macchiato .delete::before,html.theme--catppuccin-macchiato .modal-close::after,html.theme--catppuccin-macchiato .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-macchiato .modal-close::before,html.theme--catppuccin-macchiato .delete::before{height:2px;width:50%}html.theme--catppuccin-macchiato .modal-close::after,html.theme--catppuccin-macchiato .delete::after{height:50%;width:2px}html.theme--catppuccin-macchiato .modal-close:hover,html.theme--catppuccin-macchiato .delete:hover,html.theme--catppuccin-macchiato .modal-close:focus,html.theme--catppuccin-macchiato .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-macchiato .modal-close:active,html.theme--catppuccin-macchiato .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-macchiato .is-small.modal-close,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-macchiato .is-small.delete,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-macchiato .is-medium.modal-close,html.theme--catppuccin-macchiato .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-macchiato .is-large.modal-close,html.theme--catppuccin-macchiato .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-macchiato .control.is-loading::after,html.theme--catppuccin-macchiato .select.is-loading::after,html.theme--catppuccin-macchiato .loader,html.theme--catppuccin-macchiato .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #8087a2;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-macchiato .hero-video,html.theme--catppuccin-macchiato .modal-background,html.theme--catppuccin-macchiato .modal,html.theme--catppuccin-macchiato .image.is-square img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-macchiato .image.is-square .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-macchiato .image.is-1by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-macchiato .image.is-1by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-macchiato .image.is-5by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-macchiato .image.is-4by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-macchiato .image.is-3by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-macchiato .image.is-5by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-16by9 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-macchiato .image.is-16by9 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-macchiato .image.is-2by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-macchiato .image.is-3by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-macchiato .image.is-4by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-macchiato .image.is-3by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-macchiato .image.is-2by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-macchiato .image.is-3by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-9by16 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-macchiato .image.is-9by16 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-macchiato .image.is-1by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-macchiato .image.is-1by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-macchiato .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363a4f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#212431 !important}.has-background-dark{background-color:#363a4f !important}.has-text-primary{color:#8aadf4 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#5b8cf0 !important}.has-background-primary{background-color:#8aadf4 !important}.has-text-primary-light{color:#ecf2fd !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bed1f9 !important}.has-background-primary-light{background-color:#ecf2fd !important}.has-text-primary-dark{color:#0e3b95 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#124dc4 !important}.has-background-primary-dark{background-color:#0e3b95 !important}.has-text-link{color:#8aadf4 !important}a.has-text-link:hover,a.has-text-link:focus{color:#5b8cf0 !important}.has-background-link{background-color:#8aadf4 !important}.has-text-link-light{color:#ecf2fd !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bed1f9 !important}.has-background-link-light{background-color:#ecf2fd !important}.has-text-link-dark{color:#0e3b95 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#124dc4 !important}.has-background-link-dark{background-color:#0e3b95 !important}.has-text-info{color:#8bd5ca !important}a.has-text-info:hover,a.has-text-info:focus{color:#66c7b9 !important}.has-background-info{background-color:#8bd5ca !important}.has-text-info-light{color:#f0faf8 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#cbece7 !important}.has-background-info-light{background-color:#f0faf8 !important}.has-text-info-dark{color:#276d62 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#359284 !important}.has-background-info-dark{background-color:#276d62 !important}.has-text-success{color:#a6da95 !important}a.has-text-success:hover,a.has-text-success:focus{color:#86cd6f !important}.has-background-success{background-color:#a6da95 !important}.has-text-success-light{color:#f2faf0 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#d3edca !important}.has-background-success-light{background-color:#f2faf0 !important}.has-text-success-dark{color:#386e26 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#4b9333 !important}.has-background-success-dark{background-color:#386e26 !important}.has-text-warning{color:#eed49f !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#e6c174 !important}.has-background-warning{background-color:#eed49f !important}.has-text-warning-light{color:#fcf7ee !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f4e4c2 !important}.has-background-warning-light{background-color:#fcf7ee !important}.has-text-warning-dark{color:#7e5c16 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#a97b1e !important}.has-background-warning-dark{background-color:#7e5c16 !important}.has-text-danger{color:#ed8796 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#e65b6f !important}.has-background-danger{background-color:#ed8796 !important}.has-text-danger-light{color:#fcedef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f6c1c9 !important}.has-background-danger-light{background-color:#fcedef !important}.has-text-danger-dark{color:#971729 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c31d36 !important}.has-background-danger-dark{background-color:#971729 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363a4f !important}.has-background-grey-darker{background-color:#363a4f !important}.has-text-grey-dark{color:#494d64 !important}.has-background-grey-dark{background-color:#494d64 !important}.has-text-grey{color:#5b6078 !important}.has-background-grey{background-color:#5b6078 !important}.has-text-grey-light{color:#6e738d !important}.has-background-grey-light{background-color:#6e738d !important}.has-text-grey-lighter{color:#8087a2 !important}.has-background-grey-lighter{background-color:#8087a2 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-macchiato html{background-color:#24273a;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-macchiato article,html.theme--catppuccin-macchiato aside,html.theme--catppuccin-macchiato figure,html.theme--catppuccin-macchiato footer,html.theme--catppuccin-macchiato header,html.theme--catppuccin-macchiato hgroup,html.theme--catppuccin-macchiato section{display:block}html.theme--catppuccin-macchiato body,html.theme--catppuccin-macchiato button,html.theme--catppuccin-macchiato input,html.theme--catppuccin-macchiato optgroup,html.theme--catppuccin-macchiato select,html.theme--catppuccin-macchiato textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-macchiato code,html.theme--catppuccin-macchiato pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-macchiato body{color:#cad3f5;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-macchiato a{color:#8aadf4;cursor:pointer;text-decoration:none}html.theme--catppuccin-macchiato a strong{color:currentColor}html.theme--catppuccin-macchiato a:hover{color:#91d7e3}html.theme--catppuccin-macchiato code{background-color:#1e2030;color:#cad3f5;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-macchiato hr{background-color:#1e2030;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-macchiato img{height:auto;max-width:100%}html.theme--catppuccin-macchiato input[type="checkbox"],html.theme--catppuccin-macchiato input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-macchiato small{font-size:.875em}html.theme--catppuccin-macchiato span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-macchiato strong{color:#b5c1f1;font-weight:700}html.theme--catppuccin-macchiato fieldset{border:none}html.theme--catppuccin-macchiato pre{-webkit-overflow-scrolling:touch;background-color:#1e2030;color:#cad3f5;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-macchiato pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-macchiato table td,html.theme--catppuccin-macchiato table th{vertical-align:top}html.theme--catppuccin-macchiato table td:not([align]),html.theme--catppuccin-macchiato table th:not([align]){text-align:inherit}html.theme--catppuccin-macchiato table th{color:#b5c1f1}html.theme--catppuccin-macchiato .box{background-color:#494d64;border-radius:8px;box-shadow:none;color:#cad3f5;display:block;padding:1.25rem}html.theme--catppuccin-macchiato a.box:hover,html.theme--catppuccin-macchiato a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #8aadf4}html.theme--catppuccin-macchiato a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #8aadf4}html.theme--catppuccin-macchiato .button{background-color:#1e2030;border-color:#3b3f5f;border-width:1px;color:#8aadf4;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-macchiato .button strong{color:inherit}html.theme--catppuccin-macchiato .button .icon,html.theme--catppuccin-macchiato .button .icon.is-small,html.theme--catppuccin-macchiato .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-macchiato .button .icon.is-medium,html.theme--catppuccin-macchiato .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-macchiato .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-macchiato .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-macchiato .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-macchiato .button:hover,html.theme--catppuccin-macchiato .button.is-hovered{border-color:#6e738d;color:#b5c1f1}html.theme--catppuccin-macchiato .button:focus,html.theme--catppuccin-macchiato .button.is-focused{border-color:#6e738d;color:#739df2}html.theme--catppuccin-macchiato .button:focus:not(:active),html.theme--catppuccin-macchiato .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button:active,html.theme--catppuccin-macchiato .button.is-active{border-color:#494d64;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text{background-color:transparent;border-color:transparent;color:#cad3f5;text-decoration:underline}html.theme--catppuccin-macchiato .button.is-text:hover,html.theme--catppuccin-macchiato .button.is-text.is-hovered,html.theme--catppuccin-macchiato .button.is-text:focus,html.theme--catppuccin-macchiato .button.is-text.is-focused{background-color:#1e2030;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text:active,html.theme--catppuccin-macchiato .button.is-text.is-active{background-color:#141620;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-macchiato .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#8aadf4;text-decoration:none}html.theme--catppuccin-macchiato .button.is-ghost:hover,html.theme--catppuccin-macchiato .button.is-ghost.is-hovered{color:#8aadf4;text-decoration:underline}html.theme--catppuccin-macchiato .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:hover,html.theme--catppuccin-macchiato .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:focus,html.theme--catppuccin-macchiato .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:focus:not(:active),html.theme--catppuccin-macchiato .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .button.is-white:active,html.theme--catppuccin-macchiato .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-macchiato .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-macchiato .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-white.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:hover,html.theme--catppuccin-macchiato .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:focus,html.theme--catppuccin-macchiato .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:focus:not(:active),html.theme--catppuccin-macchiato .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .button.is-black:active,html.theme--catppuccin-macchiato .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-macchiato .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-black.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:hover,html.theme--catppuccin-macchiato .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:focus,html.theme--catppuccin-macchiato .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:focus:not(:active),html.theme--catppuccin-macchiato .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .button.is-light:active,html.theme--catppuccin-macchiato .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-macchiato .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-light.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-dark,html.theme--catppuccin-macchiato .content kbd.button{background-color:#363a4f;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:hover,html.theme--catppuccin-macchiato .content kbd.button:hover,html.theme--catppuccin-macchiato .button.is-dark.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-hovered{background-color:#313447;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:focus,html.theme--catppuccin-macchiato .content kbd.button:focus,html.theme--catppuccin-macchiato .button.is-dark.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:focus:not(:active),html.theme--catppuccin-macchiato .content kbd.button:focus:not(:active),html.theme--catppuccin-macchiato .button.is-dark.is-focused:not(:active),html.theme--catppuccin-macchiato .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .button.is-dark:active,html.theme--catppuccin-macchiato .content kbd.button:active,html.theme--catppuccin-macchiato .button.is-dark.is-active,html.theme--catppuccin-macchiato .content kbd.button.is-active{background-color:#2c2f40;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark[disabled],html.theme--catppuccin-macchiato .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button{background-color:#363a4f;border-color:#363a4f;box-shadow:none}html.theme--catppuccin-macchiato .button.is-dark.is-inverted,html.theme--catppuccin-macchiato .content kbd.button.is-inverted{background-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted:hover,html.theme--catppuccin-macchiato .content kbd.button.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-dark.is-inverted[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-loading::after,html.theme--catppuccin-macchiato .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined,html.theme--catppuccin-macchiato .content kbd.button.is-outlined{background-color:transparent;border-color:#363a4f;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-outlined:hover,html.theme--catppuccin-macchiato .content kbd.button.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-dark.is-outlined:focus,html.theme--catppuccin-macchiato .content kbd.button.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-focused{background-color:#363a4f;border-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363a4f #363a4f !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-outlined{background-color:transparent;border-color:#363a4f;box-shadow:none;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363a4f #363a4f !important}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:hover,html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-hovered,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:focus,html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-focused,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:focus:not(:active),html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-macchiato .button.is-primary.is-focused:not(:active),html.theme--catppuccin-macchiato details.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button.is-primary:active,html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-macchiato .button.is-primary.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-active.docs-sourcelink{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary[disabled],html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary,fieldset[disabled] html.theme--catppuccin-macchiato details.docstring>section>a.button.docs-sourcelink{background-color:#8aadf4;border-color:#8aadf4;box-shadow:none}html.theme--catppuccin-macchiato .button.is-primary.is-inverted,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted:hover,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-primary.is-inverted[disabled],html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-loading::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-outlined:hover,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-macchiato .button.is-primary.is-outlined:focus,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined[disabled],html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8aadf4;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-light,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-light.docs-sourcelink{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-primary.is-light:hover,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-light.is-hovered,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e1eafc;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-primary.is-light:active,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-macchiato .button.is-primary.is-light.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d5e2fb;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:hover,html.theme--catppuccin-macchiato .button.is-link.is-hovered{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:focus,html.theme--catppuccin-macchiato .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:focus:not(:active),html.theme--catppuccin-macchiato .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button.is-link:active,html.theme--catppuccin-macchiato .button.is-link.is-active{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link{background-color:#8aadf4;border-color:#8aadf4;box-shadow:none}html.theme--catppuccin-macchiato .button.is-link.is-inverted{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined{background-color:transparent;border-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-link.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-focused{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-outlined{background-color:transparent;border-color:#8aadf4;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-light{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link.is-light:hover,html.theme--catppuccin-macchiato .button.is-link.is-light.is-hovered{background-color:#e1eafc;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link.is-light:active,html.theme--catppuccin-macchiato .button.is-link.is-light.is-active{background-color:#d5e2fb;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-info{background-color:#8bd5ca;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:hover,html.theme--catppuccin-macchiato .button.is-info.is-hovered{background-color:#82d2c6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:focus,html.theme--catppuccin-macchiato .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:focus:not(:active),html.theme--catppuccin-macchiato .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .button.is-info:active,html.theme--catppuccin-macchiato .button.is-info.is-active{background-color:#78cec1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info{background-color:#8bd5ca;border-color:#8bd5ca;box-shadow:none}html.theme--catppuccin-macchiato .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined{background-color:transparent;border-color:#8bd5ca;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-info.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-focused{background-color:#8bd5ca;border-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #8bd5ca #8bd5ca !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-outlined{background-color:transparent;border-color:#8bd5ca;box-shadow:none;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8bd5ca #8bd5ca !important}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-light{background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .button.is-info.is-light:hover,html.theme--catppuccin-macchiato .button.is-info.is-light.is-hovered{background-color:#e7f6f4;border-color:transparent;color:#276d62}html.theme--catppuccin-macchiato .button.is-info.is-light:active,html.theme--catppuccin-macchiato .button.is-info.is-light.is-active{background-color:#ddf3f0;border-color:transparent;color:#276d62}html.theme--catppuccin-macchiato .button.is-success{background-color:#a6da95;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:hover,html.theme--catppuccin-macchiato .button.is-success.is-hovered{background-color:#9ed78c;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:focus,html.theme--catppuccin-macchiato .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:focus:not(:active),html.theme--catppuccin-macchiato .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .button.is-success:active,html.theme--catppuccin-macchiato .button.is-success.is-active{background-color:#96d382;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success{background-color:#a6da95;border-color:#a6da95;box-shadow:none}html.theme--catppuccin-macchiato .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined{background-color:transparent;border-color:#a6da95;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-success.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-focused{background-color:#a6da95;border-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6da95 #a6da95 !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-outlined{background-color:transparent;border-color:#a6da95;box-shadow:none;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6da95 #a6da95 !important}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-light{background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .button.is-success.is-light:hover,html.theme--catppuccin-macchiato .button.is-success.is-light.is-hovered{background-color:#eaf6e6;border-color:transparent;color:#386e26}html.theme--catppuccin-macchiato .button.is-success.is-light:active,html.theme--catppuccin-macchiato .button.is-success.is-light.is-active{background-color:#e2f3dd;border-color:transparent;color:#386e26}html.theme--catppuccin-macchiato .button.is-warning{background-color:#eed49f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:hover,html.theme--catppuccin-macchiato .button.is-warning.is-hovered{background-color:#eccf94;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:focus,html.theme--catppuccin-macchiato .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:focus:not(:active),html.theme--catppuccin-macchiato .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .button.is-warning:active,html.theme--catppuccin-macchiato .button.is-warning.is-active{background-color:#eaca89;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning{background-color:#eed49f;border-color:#eed49f;box-shadow:none}html.theme--catppuccin-macchiato .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined{background-color:transparent;border-color:#eed49f;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-warning.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-focused{background-color:#eed49f;border-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #eed49f #eed49f !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-outlined{background-color:transparent;border-color:#eed49f;box-shadow:none;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #eed49f #eed49f !important}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-light{background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-warning.is-light:hover,html.theme--catppuccin-macchiato .button.is-warning.is-light.is-hovered{background-color:#faf2e3;border-color:transparent;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-warning.is-light:active,html.theme--catppuccin-macchiato .button.is-warning.is-light.is-active{background-color:#f8eed8;border-color:transparent;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-danger{background-color:#ed8796;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:hover,html.theme--catppuccin-macchiato .button.is-danger.is-hovered{background-color:#eb7c8c;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:focus,html.theme--catppuccin-macchiato .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:focus:not(:active),html.theme--catppuccin-macchiato .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .button.is-danger:active,html.theme--catppuccin-macchiato .button.is-danger.is-active{background-color:#ea7183;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger{background-color:#ed8796;border-color:#ed8796;box-shadow:none}html.theme--catppuccin-macchiato .button.is-danger.is-inverted{background-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined{background-color:transparent;border-color:#ed8796;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-danger.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-focused{background-color:#ed8796;border-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #ed8796 #ed8796 !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-outlined{background-color:transparent;border-color:#ed8796;box-shadow:none;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ed8796 #ed8796 !important}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-light{background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .button.is-danger.is-light:hover,html.theme--catppuccin-macchiato .button.is-danger.is-light.is-hovered{background-color:#fbe2e6;border-color:transparent;color:#971729}html.theme--catppuccin-macchiato .button.is-danger.is-light:active,html.theme--catppuccin-macchiato .button.is-danger.is-light.is-active{background-color:#f9d7dc;border-color:transparent;color:#971729}html.theme--catppuccin-macchiato .button.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-small:not(.is-rounded),html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-macchiato .button.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .button.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .button.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button{background-color:#6e738d;border-color:#5b6078;box-shadow:none;opacity:.5}html.theme--catppuccin-macchiato .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-macchiato .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-macchiato .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-macchiato .button.is-static{background-color:#1e2030;border-color:#5b6078;color:#8087a2;box-shadow:none;pointer-events:none}html.theme--catppuccin-macchiato .button.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-macchiato .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-macchiato .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-macchiato .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-macchiato .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-macchiato .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-macchiato .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-macchiato .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-macchiato .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-macchiato .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-macchiato .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-macchiato .buttons.has-addons .button:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-macchiato .buttons.has-addons .button:focus,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-focused,html.theme--catppuccin-macchiato .buttons.has-addons .button:active,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-active,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-macchiato .buttons.has-addons .button:focus:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button:active:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-macchiato .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .buttons.is-centered{justify-content:center}html.theme--catppuccin-macchiato .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-macchiato .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .button.is-responsive.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-macchiato .button.is-responsive,html.theme--catppuccin-macchiato .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-macchiato .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .button.is-responsive.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-macchiato .button.is-responsive,html.theme--catppuccin-macchiato .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-macchiato .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-macchiato .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-macchiato .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-macchiato .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-macchiato .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-macchiato .content li+li{margin-top:0.25em}html.theme--catppuccin-macchiato .content p:not(:last-child),html.theme--catppuccin-macchiato .content dl:not(:last-child),html.theme--catppuccin-macchiato .content ol:not(:last-child),html.theme--catppuccin-macchiato .content ul:not(:last-child),html.theme--catppuccin-macchiato .content blockquote:not(:last-child),html.theme--catppuccin-macchiato .content pre:not(:last-child),html.theme--catppuccin-macchiato .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-macchiato .content h1,html.theme--catppuccin-macchiato .content h2,html.theme--catppuccin-macchiato .content h3,html.theme--catppuccin-macchiato .content h4,html.theme--catppuccin-macchiato .content h5,html.theme--catppuccin-macchiato .content h6{color:#cad3f5;font-weight:600;line-height:1.125}html.theme--catppuccin-macchiato .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-macchiato .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-macchiato .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-macchiato .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-macchiato .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-macchiato .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-macchiato .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-macchiato .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-macchiato .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-macchiato .content blockquote{background-color:#1e2030;border-left:5px solid #5b6078;padding:1.25em 1.5em}html.theme--catppuccin-macchiato .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-macchiato .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-macchiato .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-macchiato .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-macchiato .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-macchiato .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-macchiato .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-macchiato .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-macchiato .content ul ul ul{list-style-type:square}html.theme--catppuccin-macchiato .content dd{margin-left:2em}html.theme--catppuccin-macchiato .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-macchiato .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-macchiato .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-macchiato .content figure img{display:inline-block}html.theme--catppuccin-macchiato .content figure figcaption{font-style:italic}html.theme--catppuccin-macchiato .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-macchiato .content sup,html.theme--catppuccin-macchiato .content sub{font-size:75%}html.theme--catppuccin-macchiato .content table{width:100%}html.theme--catppuccin-macchiato .content table td,html.theme--catppuccin-macchiato .content table th{border:1px solid #5b6078;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-macchiato .content table th{color:#b5c1f1}html.theme--catppuccin-macchiato .content table th:not([align]){text-align:inherit}html.theme--catppuccin-macchiato .content table thead td,html.theme--catppuccin-macchiato .content table thead th{border-width:0 0 2px;color:#b5c1f1}html.theme--catppuccin-macchiato .content table tfoot td,html.theme--catppuccin-macchiato .content table tfoot th{border-width:2px 0 0;color:#b5c1f1}html.theme--catppuccin-macchiato .content table tbody tr:last-child td,html.theme--catppuccin-macchiato .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-macchiato .content .tabs li+li{margin-top:0}html.theme--catppuccin-macchiato .content.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-macchiato .content.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .content.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .content.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-macchiato .icon.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-macchiato .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-macchiato .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-macchiato .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-macchiato .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-macchiato .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-macchiato div.icon-text{display:flex}html.theme--catppuccin-macchiato .image,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-macchiato .image img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-macchiato .image img.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-macchiato .image.is-fullwidth,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .image.is-square img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-macchiato .image.is-square .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-macchiato .image.is-1by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-macchiato .image.is-1by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-macchiato .image.is-5by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-macchiato .image.is-4by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-macchiato .image.is-3by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-macchiato .image.is-5by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-16by9 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-macchiato .image.is-16by9 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-macchiato .image.is-2by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-macchiato .image.is-3by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-macchiato .image.is-4by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-macchiato .image.is-3by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-macchiato .image.is-2by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-macchiato .image.is-3by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-9by16 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-macchiato .image.is-9by16 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-macchiato .image.is-1by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-macchiato .image.is-1by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-macchiato .image.is-square,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-macchiato .image.is-1by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-macchiato .image.is-5by4,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-macchiato .image.is-4by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-macchiato .image.is-3by2,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-macchiato .image.is-5by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-macchiato .image.is-16by9,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-macchiato .image.is-2by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-macchiato .image.is-3by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-macchiato .image.is-4by5,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-macchiato .image.is-3by4,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-macchiato .image.is-2by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-macchiato .image.is-3by5,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-macchiato .image.is-9by16,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-macchiato .image.is-1by2,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-macchiato .image.is-1by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-macchiato .image.is-16x16,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-macchiato .image.is-24x24,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-macchiato .image.is-32x32,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-macchiato .image.is-48x48,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-macchiato .image.is-64x64,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-macchiato .image.is-96x96,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-macchiato .image.is-128x128,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-macchiato .notification{background-color:#1e2030;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-macchiato .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-macchiato .notification strong{color:currentColor}html.theme--catppuccin-macchiato .notification code,html.theme--catppuccin-macchiato .notification pre{background:#fff}html.theme--catppuccin-macchiato .notification pre code{background:transparent}html.theme--catppuccin-macchiato .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-macchiato .notification .title,html.theme--catppuccin-macchiato .notification .subtitle,html.theme--catppuccin-macchiato .notification .content{color:currentColor}html.theme--catppuccin-macchiato .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-dark,html.theme--catppuccin-macchiato .content kbd.notification{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .notification.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.notification.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .notification.is-primary.is-light,html.theme--catppuccin-macchiato details.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .notification.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .notification.is-link.is-light{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .notification.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-info.is-light{background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .notification.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-success.is-light{background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .notification.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-warning.is-light{background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .notification.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .notification.is-danger.is-light{background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-macchiato .progress::-webkit-progress-bar{background-color:#494d64}html.theme--catppuccin-macchiato .progress::-webkit-progress-value{background-color:#8087a2}html.theme--catppuccin-macchiato .progress::-moz-progress-bar{background-color:#8087a2}html.theme--catppuccin-macchiato .progress::-ms-fill{background-color:#8087a2;border:none}html.theme--catppuccin-macchiato .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-macchiato .content kbd.progress::-webkit-progress-value{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-macchiato .content kbd.progress::-moz-progress-bar{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark::-ms-fill,html.theme--catppuccin-macchiato .content kbd.progress::-ms-fill{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark:indeterminate,html.theme--catppuccin-macchiato .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363a4f 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-macchiato details.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-macchiato details.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary::-ms-fill,html.theme--catppuccin-macchiato details.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary:indeterminate,html.theme--catppuccin-macchiato details.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #8aadf4 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-link::-webkit-progress-value{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link::-moz-progress-bar{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link::-ms-fill{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link:indeterminate{background-image:linear-gradient(to right, #8aadf4 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-info::-webkit-progress-value{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info::-moz-progress-bar{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info::-ms-fill{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info:indeterminate{background-image:linear-gradient(to right, #8bd5ca 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-success::-webkit-progress-value{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success::-moz-progress-bar{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success::-ms-fill{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6da95 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-warning::-webkit-progress-value{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning::-moz-progress-bar{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning::-ms-fill{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #eed49f 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-danger::-webkit-progress-value{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger::-moz-progress-bar{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger::-ms-fill{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #ed8796 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#494d64;background-image:linear-gradient(to right, #cad3f5 30%, #494d64 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-macchiato .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-macchiato .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-macchiato .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-macchiato .progress.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-macchiato .progress.is-medium{height:1.25rem}html.theme--catppuccin-macchiato .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-macchiato .table{background-color:#494d64;color:#cad3f5}html.theme--catppuccin-macchiato .table td,html.theme--catppuccin-macchiato .table th{border:1px solid #5b6078;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-macchiato .table td.is-white,html.theme--catppuccin-macchiato .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .table td.is-black,html.theme--catppuccin-macchiato .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .table td.is-light,html.theme--catppuccin-macchiato .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-dark,html.theme--catppuccin-macchiato .table th.is-dark{background-color:#363a4f;border-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .table td.is-primary,html.theme--catppuccin-macchiato .table th.is-primary{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-link,html.theme--catppuccin-macchiato .table th.is-link{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-info,html.theme--catppuccin-macchiato .table th.is-info{background-color:#8bd5ca;border-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-success,html.theme--catppuccin-macchiato .table th.is-success{background-color:#a6da95;border-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-warning,html.theme--catppuccin-macchiato .table th.is-warning{background-color:#eed49f;border-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-danger,html.theme--catppuccin-macchiato .table th.is-danger{background-color:#ed8796;border-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .table td.is-narrow,html.theme--catppuccin-macchiato .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-macchiato .table td.is-selected,html.theme--catppuccin-macchiato .table th.is-selected{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-selected a,html.theme--catppuccin-macchiato .table td.is-selected strong,html.theme--catppuccin-macchiato .table th.is-selected a,html.theme--catppuccin-macchiato .table th.is-selected strong{color:currentColor}html.theme--catppuccin-macchiato .table td.is-vcentered,html.theme--catppuccin-macchiato .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-macchiato .table th{color:#b5c1f1}html.theme--catppuccin-macchiato .table th:not([align]){text-align:left}html.theme--catppuccin-macchiato .table tr.is-selected{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table tr.is-selected a,html.theme--catppuccin-macchiato .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-macchiato .table tr.is-selected td,html.theme--catppuccin-macchiato .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-macchiato .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table thead td,html.theme--catppuccin-macchiato .table thead th{border-width:0 0 2px;color:#b5c1f1}html.theme--catppuccin-macchiato .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table tfoot td,html.theme--catppuccin-macchiato .table tfoot th{border-width:2px 0 0;color:#b5c1f1}html.theme--catppuccin-macchiato .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table tbody tr:last-child td,html.theme--catppuccin-macchiato .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-macchiato .table.is-bordered td,html.theme--catppuccin-macchiato .table.is-bordered th{border-width:1px}html.theme--catppuccin-macchiato .table.is-bordered tr:last-child td,html.theme--catppuccin-macchiato .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-macchiato .table.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#363a4f}html.theme--catppuccin-macchiato .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#363a4f}html.theme--catppuccin-macchiato .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#3a3e55}html.theme--catppuccin-macchiato .table.is-narrow td,html.theme--catppuccin-macchiato .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-macchiato .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#363a4f}html.theme--catppuccin-macchiato .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-macchiato .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .tags .tag,html.theme--catppuccin-macchiato .tags .content kbd,html.theme--catppuccin-macchiato .content .tags kbd,html.theme--catppuccin-macchiato .tags details.docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-macchiato .tags .tag:not(:last-child),html.theme--catppuccin-macchiato .tags .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags kbd:not(:last-child),html.theme--catppuccin-macchiato .tags details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-macchiato .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-macchiato .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-macchiato .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .tags.are-medium details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-macchiato .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .tags.are-large details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-macchiato .tags.is-centered{justify-content:center}html.theme--catppuccin-macchiato .tags.is-centered .tag,html.theme--catppuccin-macchiato .tags.is-centered .content kbd,html.theme--catppuccin-macchiato .content .tags.is-centered kbd,html.theme--catppuccin-macchiato .tags.is-centered details.docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-macchiato .tags.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .tags.is-right .tag:not(:first-child),html.theme--catppuccin-macchiato .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-macchiato .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-macchiato .tags.is-right details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-macchiato .tags.is-right .tag:not(:last-child),html.theme--catppuccin-macchiato .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-macchiato .tags.is-right details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-macchiato .tags.has-addons .tag,html.theme--catppuccin-macchiato .tags.has-addons .content kbd,html.theme--catppuccin-macchiato .content .tags.has-addons kbd,html.theme--catppuccin-macchiato .tags.has-addons details.docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-macchiato .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-macchiato .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-macchiato .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-macchiato .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-macchiato .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-macchiato .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-macchiato .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-macchiato .tag:not(body),html.theme--catppuccin-macchiato .content kbd:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#1e2030;border-radius:.4em;color:#cad3f5;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-macchiato .tag:not(body) .delete,html.theme--catppuccin-macchiato .content kbd:not(body) .delete,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-macchiato .tag.is-white:not(body),html.theme--catppuccin-macchiato .content kbd.is-white:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .tag.is-black:not(body),html.theme--catppuccin-macchiato .content kbd.is-black:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .tag.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-dark:not(body),html.theme--catppuccin-macchiato .content kbd:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-macchiato .content details.docstring>section>kbd:not(body){background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .tag.is-primary:not(body),html.theme--catppuccin-macchiato .content kbd.is-primary:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:not(body){background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .tag.is-primary.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .tag.is-link:not(body),html.theme--catppuccin-macchiato .content kbd.is-link:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .tag.is-link.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-link.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .tag.is-info:not(body),html.theme--catppuccin-macchiato .content kbd.is-info:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-info.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-info.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .tag.is-success:not(body),html.theme--catppuccin-macchiato .content kbd.is-success:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-success.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-success.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .tag.is-warning:not(body),html.theme--catppuccin-macchiato .content kbd.is-warning:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-warning.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .tag.is-danger:not(body),html.theme--catppuccin-macchiato .content kbd.is-danger:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .tag.is-danger.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .tag.is-normal:not(body),html.theme--catppuccin-macchiato .content kbd.is-normal:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-macchiato .tag.is-medium:not(body),html.theme--catppuccin-macchiato .content kbd.is-medium:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-macchiato .tag.is-large:not(body),html.theme--catppuccin-macchiato .content kbd.is-large:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-macchiato .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-macchiato .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-macchiato .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-macchiato .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-macchiato .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-macchiato .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-macchiato .tag.is-delete:not(body),html.theme--catppuccin-macchiato .content kbd.is-delete:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::before,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::before,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-macchiato .tag.is-delete:not(body)::after,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::after,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::before,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::before,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::after,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::after,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-macchiato .tag.is-delete:not(body):hover,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):hover,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-macchiato .tag.is-delete:not(body):focus,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):focus,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#141620}html.theme--catppuccin-macchiato .tag.is-delete:not(body):active,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):active,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#0a0b11}html.theme--catppuccin-macchiato .tag.is-rounded:not(body),html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-macchiato .content kbd.is-rounded:not(body),html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-macchiato a.tag:hover,html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-macchiato .title,html.theme--catppuccin-macchiato .subtitle{word-break:break-word}html.theme--catppuccin-macchiato .title em,html.theme--catppuccin-macchiato .title span,html.theme--catppuccin-macchiato .subtitle em,html.theme--catppuccin-macchiato .subtitle span{font-weight:inherit}html.theme--catppuccin-macchiato .title sub,html.theme--catppuccin-macchiato .subtitle sub{font-size:.75em}html.theme--catppuccin-macchiato .title sup,html.theme--catppuccin-macchiato .subtitle sup{font-size:.75em}html.theme--catppuccin-macchiato .title .tag,html.theme--catppuccin-macchiato .title .content kbd,html.theme--catppuccin-macchiato .content .title kbd,html.theme--catppuccin-macchiato .title details.docstring>section>a.docs-sourcelink,html.theme--catppuccin-macchiato .subtitle .tag,html.theme--catppuccin-macchiato .subtitle .content kbd,html.theme--catppuccin-macchiato .content .subtitle kbd,html.theme--catppuccin-macchiato .subtitle details.docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-macchiato .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-macchiato .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-macchiato .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-macchiato .title.is-1{font-size:3rem}html.theme--catppuccin-macchiato .title.is-2{font-size:2.5rem}html.theme--catppuccin-macchiato .title.is-3{font-size:2rem}html.theme--catppuccin-macchiato .title.is-4{font-size:1.5rem}html.theme--catppuccin-macchiato .title.is-5{font-size:1.25rem}html.theme--catppuccin-macchiato .title.is-6{font-size:1rem}html.theme--catppuccin-macchiato .title.is-7{font-size:.75rem}html.theme--catppuccin-macchiato .subtitle{color:#6e738d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-macchiato .subtitle strong{color:#6e738d;font-weight:600}html.theme--catppuccin-macchiato .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-macchiato .subtitle.is-1{font-size:3rem}html.theme--catppuccin-macchiato .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-macchiato .subtitle.is-3{font-size:2rem}html.theme--catppuccin-macchiato .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-macchiato .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-macchiato .subtitle.is-6{font-size:1rem}html.theme--catppuccin-macchiato .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-macchiato .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-macchiato .number{align-items:center;background-color:#1e2030;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{background-color:#24273a;border-color:#5b6078;border-radius:.4em;color:#8087a2}html.theme--catppuccin-macchiato .select select::-moz-placeholder,html.theme--catppuccin-macchiato .textarea::-moz-placeholder,html.theme--catppuccin-macchiato .input::-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select::-webkit-input-placeholder,html.theme--catppuccin-macchiato .textarea::-webkit-input-placeholder,html.theme--catppuccin-macchiato .input::-webkit-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:-moz-placeholder,html.theme--catppuccin-macchiato .textarea:-moz-placeholder,html.theme--catppuccin-macchiato .input:-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:-ms-input-placeholder,html.theme--catppuccin-macchiato .textarea:-ms-input-placeholder,html.theme--catppuccin-macchiato .input:-ms-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:hover,html.theme--catppuccin-macchiato .textarea:hover,html.theme--catppuccin-macchiato .input:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-macchiato .select select.is-hovered,html.theme--catppuccin-macchiato .is-hovered.textarea,html.theme--catppuccin-macchiato .is-hovered.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#6e738d}html.theme--catppuccin-macchiato .select select:focus,html.theme--catppuccin-macchiato .textarea:focus,html.theme--catppuccin-macchiato .input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-macchiato .select select.is-focused,html.theme--catppuccin-macchiato .is-focused.textarea,html.theme--catppuccin-macchiato .is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .select select:active,html.theme--catppuccin-macchiato .textarea:active,html.theme--catppuccin-macchiato .input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-macchiato .select select.is-active,html.theme--catppuccin-macchiato .is-active.textarea,html.theme--catppuccin-macchiato .is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#8aadf4;box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select select[disabled],html.theme--catppuccin-macchiato .textarea[disabled],html.theme--catppuccin-macchiato .input[disabled],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .select select,fieldset[disabled] html.theme--catppuccin-macchiato .textarea,fieldset[disabled] html.theme--catppuccin-macchiato .input,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{background-color:#6e738d;border-color:#1e2030;box-shadow:none;color:#f5f7fd}html.theme--catppuccin-macchiato .select select[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato .input[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato .input[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-macchiato .textarea[readonly],html.theme--catppuccin-macchiato .input[readonly],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-macchiato .is-white.textarea,html.theme--catppuccin-macchiato .is-white.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-macchiato .is-white.textarea:focus,html.theme--catppuccin-macchiato .is-white.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-macchiato .is-white.is-focused.textarea,html.theme--catppuccin-macchiato .is-white.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-white.textarea:active,html.theme--catppuccin-macchiato .is-white.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-macchiato .is-white.is-active.textarea,html.theme--catppuccin-macchiato .is-white.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .is-black.textarea,html.theme--catppuccin-macchiato .is-black.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-macchiato .is-black.textarea:focus,html.theme--catppuccin-macchiato .is-black.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-macchiato .is-black.is-focused.textarea,html.theme--catppuccin-macchiato .is-black.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-black.textarea:active,html.theme--catppuccin-macchiato .is-black.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-macchiato .is-black.is-active.textarea,html.theme--catppuccin-macchiato .is-black.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .is-light.textarea,html.theme--catppuccin-macchiato .is-light.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-macchiato .is-light.textarea:focus,html.theme--catppuccin-macchiato .is-light.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-macchiato .is-light.is-focused.textarea,html.theme--catppuccin-macchiato .is-light.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-light.textarea:active,html.theme--catppuccin-macchiato .is-light.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-macchiato .is-light.is-active.textarea,html.theme--catppuccin-macchiato .is-light.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .is-dark.textarea,html.theme--catppuccin-macchiato .content kbd.textarea,html.theme--catppuccin-macchiato .is-dark.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-macchiato .content kbd.input{border-color:#363a4f}html.theme--catppuccin-macchiato .is-dark.textarea:focus,html.theme--catppuccin-macchiato .content kbd.textarea:focus,html.theme--catppuccin-macchiato .is-dark.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-macchiato .content kbd.input:focus,html.theme--catppuccin-macchiato .is-dark.is-focused.textarea,html.theme--catppuccin-macchiato .content kbd.is-focused.textarea,html.theme--catppuccin-macchiato .is-dark.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .content kbd.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-dark.textarea:active,html.theme--catppuccin-macchiato .content kbd.textarea:active,html.theme--catppuccin-macchiato .is-dark.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-macchiato .content kbd.input:active,html.theme--catppuccin-macchiato .is-dark.is-active.textarea,html.theme--catppuccin-macchiato .content kbd.is-active.textarea,html.theme--catppuccin-macchiato .is-dark.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .content kbd.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .is-primary.textarea,html.theme--catppuccin-macchiato details.docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.input.docs-sourcelink{border-color:#8aadf4}html.theme--catppuccin-macchiato .is-primary.textarea:focus,html.theme--catppuccin-macchiato details.docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-macchiato .is-primary.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-macchiato details.docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-macchiato .is-primary.is-focused.textarea,html.theme--catppuccin-macchiato details.docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato details.docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.textarea:active,html.theme--catppuccin-macchiato details.docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-macchiato .is-primary.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-macchiato details.docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-macchiato .is-primary.is-active.textarea,html.theme--catppuccin-macchiato details.docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .is-link.textarea,html.theme--catppuccin-macchiato .is-link.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#8aadf4}html.theme--catppuccin-macchiato .is-link.textarea:focus,html.theme--catppuccin-macchiato .is-link.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-macchiato .is-link.is-focused.textarea,html.theme--catppuccin-macchiato .is-link.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-link.textarea:active,html.theme--catppuccin-macchiato .is-link.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-macchiato .is-link.is-active.textarea,html.theme--catppuccin-macchiato .is-link.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .is-info.textarea,html.theme--catppuccin-macchiato .is-info.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#8bd5ca}html.theme--catppuccin-macchiato .is-info.textarea:focus,html.theme--catppuccin-macchiato .is-info.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-macchiato .is-info.is-focused.textarea,html.theme--catppuccin-macchiato .is-info.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-info.textarea:active,html.theme--catppuccin-macchiato .is-info.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-macchiato .is-info.is-active.textarea,html.theme--catppuccin-macchiato .is-info.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .is-success.textarea,html.theme--catppuccin-macchiato .is-success.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6da95}html.theme--catppuccin-macchiato .is-success.textarea:focus,html.theme--catppuccin-macchiato .is-success.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-macchiato .is-success.is-focused.textarea,html.theme--catppuccin-macchiato .is-success.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-success.textarea:active,html.theme--catppuccin-macchiato .is-success.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-macchiato .is-success.is-active.textarea,html.theme--catppuccin-macchiato .is-success.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .is-warning.textarea,html.theme--catppuccin-macchiato .is-warning.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#eed49f}html.theme--catppuccin-macchiato .is-warning.textarea:focus,html.theme--catppuccin-macchiato .is-warning.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-macchiato .is-warning.is-focused.textarea,html.theme--catppuccin-macchiato .is-warning.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-warning.textarea:active,html.theme--catppuccin-macchiato .is-warning.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-macchiato .is-warning.is-active.textarea,html.theme--catppuccin-macchiato .is-warning.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .is-danger.textarea,html.theme--catppuccin-macchiato .is-danger.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#ed8796}html.theme--catppuccin-macchiato .is-danger.textarea:focus,html.theme--catppuccin-macchiato .is-danger.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-macchiato .is-danger.is-focused.textarea,html.theme--catppuccin-macchiato .is-danger.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-danger.textarea:active,html.theme--catppuccin-macchiato .is-danger.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-macchiato .is-danger.is-active.textarea,html.theme--catppuccin-macchiato .is-danger.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .is-small.textarea,html.theme--catppuccin-macchiato .is-small.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-macchiato .is-medium.textarea,html.theme--catppuccin-macchiato .is-medium.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .is-large.textarea,html.theme--catppuccin-macchiato .is-large.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .is-fullwidth.textarea,html.theme--catppuccin-macchiato .is-fullwidth.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-macchiato .is-inline.textarea,html.theme--catppuccin-macchiato .is-inline.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-macchiato .input.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-macchiato .input.is-static,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-macchiato .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-macchiato .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-macchiato .textarea[rows]{height:initial}html.theme--catppuccin-macchiato .textarea.has-fixed-size{resize:none}html.theme--catppuccin-macchiato .radio,html.theme--catppuccin-macchiato .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-macchiato .radio input,html.theme--catppuccin-macchiato .checkbox input{cursor:pointer}html.theme--catppuccin-macchiato .radio:hover,html.theme--catppuccin-macchiato .checkbox:hover{color:#91d7e3}html.theme--catppuccin-macchiato .radio[disabled],html.theme--catppuccin-macchiato .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .radio,fieldset[disabled] html.theme--catppuccin-macchiato .checkbox,html.theme--catppuccin-macchiato .radio input[disabled],html.theme--catppuccin-macchiato .checkbox input[disabled]{color:#f5f7fd;cursor:not-allowed}html.theme--catppuccin-macchiato .radio+.radio{margin-left:.5em}html.theme--catppuccin-macchiato .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-macchiato .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading)::after{border-color:#8aadf4;right:1.125em;z-index:4}html.theme--catppuccin-macchiato .select.is-rounded select,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-macchiato .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-macchiato .select select::-ms-expand{display:none}html.theme--catppuccin-macchiato .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-macchiato .select select:hover{border-color:#1e2030}html.theme--catppuccin-macchiato .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-macchiato .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-macchiato .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#91d7e3}html.theme--catppuccin-macchiato .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-macchiato .select.is-white select{border-color:#fff}html.theme--catppuccin-macchiato .select.is-white select:hover,html.theme--catppuccin-macchiato .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-macchiato .select.is-white select:focus,html.theme--catppuccin-macchiato .select.is-white select.is-focused,html.theme--catppuccin-macchiato .select.is-white select:active,html.theme--catppuccin-macchiato .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-macchiato .select.is-black select:hover,html.theme--catppuccin-macchiato .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-macchiato .select.is-black select:focus,html.theme--catppuccin-macchiato .select.is-black select.is-focused,html.theme--catppuccin-macchiato .select.is-black select:active,html.theme--catppuccin-macchiato .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-macchiato .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-macchiato .select.is-light select:hover,html.theme--catppuccin-macchiato .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-macchiato .select.is-light select:focus,html.theme--catppuccin-macchiato .select.is-light select.is-focused,html.theme--catppuccin-macchiato .select.is-light select:active,html.theme--catppuccin-macchiato .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .select.is-dark:not(:hover)::after,html.theme--catppuccin-macchiato .content kbd.select:not(:hover)::after{border-color:#363a4f}html.theme--catppuccin-macchiato .select.is-dark select,html.theme--catppuccin-macchiato .content kbd.select select{border-color:#363a4f}html.theme--catppuccin-macchiato .select.is-dark select:hover,html.theme--catppuccin-macchiato .content kbd.select select:hover,html.theme--catppuccin-macchiato .select.is-dark select.is-hovered,html.theme--catppuccin-macchiato .content kbd.select select.is-hovered{border-color:#2c2f40}html.theme--catppuccin-macchiato .select.is-dark select:focus,html.theme--catppuccin-macchiato .content kbd.select select:focus,html.theme--catppuccin-macchiato .select.is-dark select.is-focused,html.theme--catppuccin-macchiato .content kbd.select select.is-focused,html.theme--catppuccin-macchiato .select.is-dark select:active,html.theme--catppuccin-macchiato .content kbd.select select:active,html.theme--catppuccin-macchiato .select.is-dark select.is-active,html.theme--catppuccin-macchiato .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .select.is-primary:not(:hover)::after,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-primary select,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-primary select:hover,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-macchiato .select.is-primary select.is-hovered,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#739df2}html.theme--catppuccin-macchiato .select.is-primary select:focus,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-macchiato .select.is-primary select.is-focused,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-macchiato .select.is-primary select:active,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-macchiato .select.is-primary select.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select.is-link:not(:hover)::after{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-link select{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-link select:hover,html.theme--catppuccin-macchiato .select.is-link select.is-hovered{border-color:#739df2}html.theme--catppuccin-macchiato .select.is-link select:focus,html.theme--catppuccin-macchiato .select.is-link select.is-focused,html.theme--catppuccin-macchiato .select.is-link select:active,html.theme--catppuccin-macchiato .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select.is-info:not(:hover)::after{border-color:#8bd5ca}html.theme--catppuccin-macchiato .select.is-info select{border-color:#8bd5ca}html.theme--catppuccin-macchiato .select.is-info select:hover,html.theme--catppuccin-macchiato .select.is-info select.is-hovered{border-color:#78cec1}html.theme--catppuccin-macchiato .select.is-info select:focus,html.theme--catppuccin-macchiato .select.is-info select.is-focused,html.theme--catppuccin-macchiato .select.is-info select:active,html.theme--catppuccin-macchiato .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .select.is-success:not(:hover)::after{border-color:#a6da95}html.theme--catppuccin-macchiato .select.is-success select{border-color:#a6da95}html.theme--catppuccin-macchiato .select.is-success select:hover,html.theme--catppuccin-macchiato .select.is-success select.is-hovered{border-color:#96d382}html.theme--catppuccin-macchiato .select.is-success select:focus,html.theme--catppuccin-macchiato .select.is-success select.is-focused,html.theme--catppuccin-macchiato .select.is-success select:active,html.theme--catppuccin-macchiato .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .select.is-warning:not(:hover)::after{border-color:#eed49f}html.theme--catppuccin-macchiato .select.is-warning select{border-color:#eed49f}html.theme--catppuccin-macchiato .select.is-warning select:hover,html.theme--catppuccin-macchiato .select.is-warning select.is-hovered{border-color:#eaca89}html.theme--catppuccin-macchiato .select.is-warning select:focus,html.theme--catppuccin-macchiato .select.is-warning select.is-focused,html.theme--catppuccin-macchiato .select.is-warning select:active,html.theme--catppuccin-macchiato .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .select.is-danger:not(:hover)::after{border-color:#ed8796}html.theme--catppuccin-macchiato .select.is-danger select{border-color:#ed8796}html.theme--catppuccin-macchiato .select.is-danger select:hover,html.theme--catppuccin-macchiato .select.is-danger select.is-hovered{border-color:#ea7183}html.theme--catppuccin-macchiato .select.is-danger select:focus,html.theme--catppuccin-macchiato .select.is-danger select.is-focused,html.theme--catppuccin-macchiato .select.is-danger select:active,html.theme--catppuccin-macchiato .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .select.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-macchiato .select.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .select.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .select.is-disabled::after{border-color:#f5f7fd !important;opacity:0.5}html.theme--catppuccin-macchiato .select.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .select.is-fullwidth select{width:100%}html.theme--catppuccin-macchiato .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-macchiato .select.is-loading.is-small:after,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-macchiato .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-macchiato .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-macchiato .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-macchiato .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:hover .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:focus .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:active .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-black:hover .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-black:focus .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-black:active .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:hover .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:focus .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:active .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-dark .file-cta,html.theme--catppuccin-macchiato .content kbd.file .file-cta{background-color:#363a4f;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-dark:hover .file-cta,html.theme--catppuccin-macchiato .content kbd.file:hover .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-hovered .file-cta{background-color:#313447;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-dark:focus .file-cta,html.theme--catppuccin-macchiato .content kbd.file:focus .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-focused .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,58,79,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-dark:active .file-cta,html.theme--catppuccin-macchiato .content kbd.file:active .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-active .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-active .file-cta{background-color:#2c2f40;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary:hover .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary:focus .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-focused .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(138,173,244,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-primary:active .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-active .file-cta,html.theme--catppuccin-macchiato details.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link .file-cta{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link:hover .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-hovered .file-cta{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link:focus .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(138,173,244,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-link:active .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-active .file-cta{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-info .file-cta{background-color:#8bd5ca;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:hover .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-hovered .file-cta{background-color:#82d2c6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:focus .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(139,213,202,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:active .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-active .file-cta{background-color:#78cec1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success .file-cta{background-color:#a6da95;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:hover .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-hovered .file-cta{background-color:#9ed78c;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:focus .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,218,149,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:active .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-active .file-cta{background-color:#96d382;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning .file-cta{background-color:#eed49f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:hover .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-hovered .file-cta{background-color:#eccf94;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:focus .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(238,212,159,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:active .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-active .file-cta{background-color:#eaca89;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-danger .file-cta{background-color:#ed8796;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-danger:hover .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-hovered .file-cta{background-color:#eb7c8c;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-danger:focus .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(237,135,150,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-danger:active .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-active .file-cta{background-color:#ea7183;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-macchiato .file.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .file.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-macchiato .file.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-macchiato .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-macchiato .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-macchiato .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-macchiato .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-macchiato .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-macchiato .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-macchiato .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-macchiato .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-macchiato .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-macchiato .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-macchiato .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-macchiato .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-macchiato .file.is-centered{justify-content:center}html.theme--catppuccin-macchiato .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-macchiato .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-macchiato .file.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-macchiato .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-macchiato .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-macchiato .file-label:hover .file-cta{background-color:#313447;color:#b5c1f1}html.theme--catppuccin-macchiato .file-label:hover .file-name{border-color:#565a71}html.theme--catppuccin-macchiato .file-label:active .file-cta{background-color:#2c2f40;color:#b5c1f1}html.theme--catppuccin-macchiato .file-label:active .file-name{border-color:#505469}html.theme--catppuccin-macchiato .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato .file-name{border-color:#5b6078;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-macchiato .file-cta{background-color:#363a4f;color:#cad3f5}html.theme--catppuccin-macchiato .file-name{border-color:#5b6078;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-macchiato .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-macchiato .file-icon .fa{font-size:14px}html.theme--catppuccin-macchiato .label{color:#b5c1f1;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-macchiato .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-macchiato .label.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-macchiato .label.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .label.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-macchiato .help.is-white{color:#fff}html.theme--catppuccin-macchiato .help.is-black{color:#0a0a0a}html.theme--catppuccin-macchiato .help.is-light{color:#f5f5f5}html.theme--catppuccin-macchiato .help.is-dark,html.theme--catppuccin-macchiato .content kbd.help{color:#363a4f}html.theme--catppuccin-macchiato .help.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.help.docs-sourcelink{color:#8aadf4}html.theme--catppuccin-macchiato .help.is-link{color:#8aadf4}html.theme--catppuccin-macchiato .help.is-info{color:#8bd5ca}html.theme--catppuccin-macchiato .help.is-success{color:#a6da95}html.theme--catppuccin-macchiato .help.is-warning{color:#eed49f}html.theme--catppuccin-macchiato .help.is-danger{color:#ed8796}html.theme--catppuccin-macchiato .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-macchiato .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-macchiato .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-macchiato .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-macchiato .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-macchiato .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-macchiato .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-macchiato .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-macchiato .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field.is-horizontal{display:flex}}html.theme--catppuccin-macchiato .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-macchiato .field-label.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-macchiato .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-macchiato .field-body .field{margin-bottom:0}html.theme--catppuccin-macchiato .field-body>.field{flex-shrink:1}html.theme--catppuccin-macchiato .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-macchiato .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-macchiato .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-macchiato .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select:focus~.icon{color:#363a4f}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-macchiato .control.has-icons-left .icon,html.theme--catppuccin-macchiato .control.has-icons-right .icon{color:#5b6078;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-macchiato .control.has-icons-left .input,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-macchiato .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-macchiato .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-macchiato .control.has-icons-right .input,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-macchiato .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-macchiato .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-macchiato .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-macchiato .control.is-loading.is-small:after,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-macchiato .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-macchiato .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-macchiato .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-macchiato .breadcrumb a{align-items:center;color:#8aadf4;display:initial;justify-content:center;padding:0 .75em}html.theme--catppuccin-macchiato .breadcrumb a:hover{color:#91d7e3}html.theme--catppuccin-macchiato .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-macchiato .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-macchiato .breadcrumb li.is-active a{color:#b5c1f1;cursor:default;pointer-events:none}html.theme--catppuccin-macchiato .breadcrumb li+li::before{color:#6e738d;content:"\0002f"}html.theme--catppuccin-macchiato .breadcrumb ul,html.theme--catppuccin-macchiato .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-macchiato .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-macchiato .breadcrumb.is-centered ol,html.theme--catppuccin-macchiato .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-macchiato .breadcrumb.is-right ol,html.theme--catppuccin-macchiato .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-macchiato .breadcrumb.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-macchiato .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-macchiato .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-macchiato .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-macchiato .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-macchiato .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#cad3f5;max-width:100%;position:relative}html.theme--catppuccin-macchiato .card-footer:first-child,html.theme--catppuccin-macchiato .card-content:first-child,html.theme--catppuccin-macchiato .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-macchiato .card-footer:last-child,html.theme--catppuccin-macchiato .card-content:last-child,html.theme--catppuccin-macchiato .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-macchiato .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-macchiato .card-header-title{align-items:center;color:#b5c1f1;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-macchiato .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-macchiato .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-macchiato .card-image{display:block;position:relative}html.theme--catppuccin-macchiato .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-macchiato .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-macchiato .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-macchiato .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-macchiato .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-macchiato .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-macchiato .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-macchiato .dropdown.is-active .dropdown-menu,html.theme--catppuccin-macchiato .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-macchiato .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-macchiato .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-macchiato .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-macchiato .dropdown-content{background-color:#1e2030;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-macchiato .dropdown-item{color:#cad3f5;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-macchiato a.dropdown-item,html.theme--catppuccin-macchiato button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-macchiato a.dropdown-item:hover,html.theme--catppuccin-macchiato button.dropdown-item:hover{background-color:#1e2030;color:#0a0a0a}html.theme--catppuccin-macchiato a.dropdown-item.is-active,html.theme--catppuccin-macchiato button.dropdown-item.is-active{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-macchiato .level{align-items:center;justify-content:space-between}html.theme--catppuccin-macchiato .level code{border-radius:.4em}html.theme--catppuccin-macchiato .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-macchiato .level.is-mobile{display:flex}html.theme--catppuccin-macchiato .level.is-mobile .level-left,html.theme--catppuccin-macchiato .level.is-mobile .level-right{display:flex}html.theme--catppuccin-macchiato .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-macchiato .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-macchiato .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level{display:flex}html.theme--catppuccin-macchiato .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-macchiato .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-macchiato .level-item .title,html.theme--catppuccin-macchiato .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-macchiato .level-left,html.theme--catppuccin-macchiato .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .level-left .level-item.is-flexible,html.theme--catppuccin-macchiato .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-left .level-item:not(:last-child),html.theme--catppuccin-macchiato .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-macchiato .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-left{display:flex}}html.theme--catppuccin-macchiato .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-right{display:flex}}html.theme--catppuccin-macchiato .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-macchiato .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-macchiato .media .media{border-top:1px solid rgba(91,96,120,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-macchiato .media .media .content:not(:last-child),html.theme--catppuccin-macchiato .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-macchiato .media .media .media{padding-top:.5rem}html.theme--catppuccin-macchiato .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-macchiato .media+.media{border-top:1px solid rgba(91,96,120,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-macchiato .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-macchiato .media-left,html.theme--catppuccin-macchiato .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .media-left{margin-right:1rem}html.theme--catppuccin-macchiato .media-right{margin-left:1rem}html.theme--catppuccin-macchiato .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .media-content{overflow-x:auto}}html.theme--catppuccin-macchiato .menu{font-size:1rem}html.theme--catppuccin-macchiato .menu.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-macchiato .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .menu.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .menu-list{line-height:1.25}html.theme--catppuccin-macchiato .menu-list a{border-radius:3px;color:#cad3f5;display:block;padding:0.5em 0.75em}html.theme--catppuccin-macchiato .menu-list a:hover{background-color:#1e2030;color:#b5c1f1}html.theme--catppuccin-macchiato .menu-list a.is-active{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .menu-list li ul{border-left:1px solid #5b6078;margin:.75em;padding-left:.75em}html.theme--catppuccin-macchiato .menu-label{color:#f5f7fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-macchiato .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-macchiato .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-macchiato .message{background-color:#1e2030;border-radius:.4em;font-size:1rem}html.theme--catppuccin-macchiato .message strong{color:currentColor}html.theme--catppuccin-macchiato .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-macchiato .message.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-macchiato .message.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .message.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .message.is-white{background-color:#fff}html.theme--catppuccin-macchiato .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-macchiato .message.is-black{background-color:#fafafa}html.theme--catppuccin-macchiato .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-macchiato .message.is-light{background-color:#fafafa}html.theme--catppuccin-macchiato .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-macchiato .message.is-dark,html.theme--catppuccin-macchiato .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-macchiato .message.is-dark .message-header,html.theme--catppuccin-macchiato .content kbd.message .message-header{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .message.is-dark .message-body,html.theme--catppuccin-macchiato .content kbd.message .message-body{border-color:#363a4f}html.theme--catppuccin-macchiato .message.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.message.docs-sourcelink{background-color:#ecf2fd}html.theme--catppuccin-macchiato .message.is-primary .message-header,html.theme--catppuccin-macchiato details.docstring>section>a.message.docs-sourcelink .message-header{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .message.is-primary .message-body,html.theme--catppuccin-macchiato details.docstring>section>a.message.docs-sourcelink .message-body{border-color:#8aadf4;color:#0e3b95}html.theme--catppuccin-macchiato .message.is-link{background-color:#ecf2fd}html.theme--catppuccin-macchiato .message.is-link .message-header{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .message.is-link .message-body{border-color:#8aadf4;color:#0e3b95}html.theme--catppuccin-macchiato .message.is-info{background-color:#f0faf8}html.theme--catppuccin-macchiato .message.is-info .message-header{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-info .message-body{border-color:#8bd5ca;color:#276d62}html.theme--catppuccin-macchiato .message.is-success{background-color:#f2faf0}html.theme--catppuccin-macchiato .message.is-success .message-header{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-success .message-body{border-color:#a6da95;color:#386e26}html.theme--catppuccin-macchiato .message.is-warning{background-color:#fcf7ee}html.theme--catppuccin-macchiato .message.is-warning .message-header{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-warning .message-body{border-color:#eed49f;color:#7e5c16}html.theme--catppuccin-macchiato .message.is-danger{background-color:#fcedef}html.theme--catppuccin-macchiato .message.is-danger .message-header{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .message.is-danger .message-body{border-color:#ed8796;color:#971729}html.theme--catppuccin-macchiato .message-header{align-items:center;background-color:#cad3f5;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-macchiato .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-macchiato .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .message-body{border-color:#5b6078;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#cad3f5;padding:1.25em 1.5em}html.theme--catppuccin-macchiato .message-body code,html.theme--catppuccin-macchiato .message-body pre{background-color:#fff}html.theme--catppuccin-macchiato .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-macchiato .modal.is-active{display:flex}html.theme--catppuccin-macchiato .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-macchiato .modal-content,html.theme--catppuccin-macchiato .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-macchiato .modal-content,html.theme--catppuccin-macchiato .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-macchiato .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-macchiato .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-macchiato .modal-card-head,html.theme--catppuccin-macchiato .modal-card-foot{align-items:center;background-color:#1e2030;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-macchiato .modal-card-head{border-bottom:1px solid #5b6078;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-macchiato .modal-card-title{color:#cad3f5;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-macchiato .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5b6078}html.theme--catppuccin-macchiato .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-macchiato .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#24273a;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-macchiato .navbar{background-color:#8aadf4;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-macchiato .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-macchiato .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-dark,html.theme--catppuccin-macchiato .content kbd.navbar{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-burger,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363a4f;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-burger,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6da95;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#eed49f;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#ed8796;color:#fff}}html.theme--catppuccin-macchiato .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-macchiato .navbar.has-shadow{box-shadow:0 2px 0 0 #1e2030}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom,html.theme--catppuccin-macchiato .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #1e2030}html.theme--catppuccin-macchiato .navbar.is-fixed-top{top:0}html.theme--catppuccin-macchiato html.has-navbar-fixed-top,html.theme--catppuccin-macchiato body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-macchiato .navbar-brand,html.theme--catppuccin-macchiato .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-macchiato .navbar-brand a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-macchiato .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-macchiato .navbar-burger{color:#cad3f5;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-macchiato .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-macchiato .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-macchiato .navbar-menu{display:none}html.theme--catppuccin-macchiato .navbar-item,html.theme--catppuccin-macchiato .navbar-link{color:#cad3f5;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-macchiato .navbar-item .icon:only-child,html.theme--catppuccin-macchiato .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-macchiato a.navbar-item,html.theme--catppuccin-macchiato .navbar-link{cursor:pointer}html.theme--catppuccin-macchiato a.navbar-item:focus,html.theme--catppuccin-macchiato a.navbar-item:focus-within,html.theme--catppuccin-macchiato a.navbar-item:hover,html.theme--catppuccin-macchiato a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar-link:focus,html.theme--catppuccin-macchiato .navbar-link:focus-within,html.theme--catppuccin-macchiato .navbar-link:hover,html.theme--catppuccin-macchiato .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}html.theme--catppuccin-macchiato .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .navbar-item img{max-height:1.75rem}html.theme--catppuccin-macchiato .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-macchiato .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-macchiato .navbar-item.is-tab:focus,html.theme--catppuccin-macchiato .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#8aadf4;border-bottom-style:solid;border-bottom-width:3px;color:#8aadf4;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-macchiato .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-macchiato .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-macchiato .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-macchiato .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .navbar>.container{display:block}html.theme--catppuccin-macchiato .navbar-brand .navbar-item,html.theme--catppuccin-macchiato .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-macchiato .navbar-link::after{display:none}html.theme--catppuccin-macchiato .navbar-menu{background-color:#8aadf4;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-macchiato .navbar-menu.is-active{display:block}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch,html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-macchiato .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-macchiato html.has-navbar-fixed-top-touch,html.theme--catppuccin-macchiato body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar,html.theme--catppuccin-macchiato .navbar-menu,html.theme--catppuccin-macchiato .navbar-start,html.theme--catppuccin-macchiato .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-macchiato .navbar{min-height:4rem}html.theme--catppuccin-macchiato .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-start,html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-macchiato .navbar.is-spaced a.navbar-item,html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8087a2}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}html.theme--catppuccin-macchiato .navbar-burger{display:none}html.theme--catppuccin-macchiato .navbar-item,html.theme--catppuccin-macchiato .navbar-link{align-items:center;display:flex}html.theme--catppuccin-macchiato .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-macchiato .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-macchiato .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-macchiato .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-macchiato .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-macchiato .navbar-dropdown{background-color:#8aadf4;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-macchiato .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8087a2}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-macchiato .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-macchiato .navbar-divider{display:block}html.theme--catppuccin-macchiato .navbar>.container .navbar-brand,html.theme--catppuccin-macchiato .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-macchiato .navbar>.container .navbar-menu,html.theme--catppuccin-macchiato .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-macchiato .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-macchiato html.has-navbar-fixed-top-desktop,html.theme--catppuccin-macchiato body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-macchiato html.has-spaced-navbar-fixed-top,html.theme--catppuccin-macchiato body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-macchiato html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-macchiato body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-macchiato a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar-link.is-active{color:#8aadf4}html.theme--catppuccin-macchiato a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-macchiato .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-macchiato .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-macchiato .pagination.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-macchiato .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-previous,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-next,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-link,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-macchiato .pagination,html.theme--catppuccin-macchiato .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link{border-color:#5b6078;color:#8aadf4;min-width:2.5em}html.theme--catppuccin-macchiato .pagination-previous:hover,html.theme--catppuccin-macchiato .pagination-next:hover,html.theme--catppuccin-macchiato .pagination-link:hover{border-color:#6e738d;color:#91d7e3}html.theme--catppuccin-macchiato .pagination-previous:focus,html.theme--catppuccin-macchiato .pagination-next:focus,html.theme--catppuccin-macchiato .pagination-link:focus{border-color:#6e738d}html.theme--catppuccin-macchiato .pagination-previous:active,html.theme--catppuccin-macchiato .pagination-next:active,html.theme--catppuccin-macchiato .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-macchiato .pagination-previous[disabled],html.theme--catppuccin-macchiato .pagination-previous.is-disabled,html.theme--catppuccin-macchiato .pagination-next[disabled],html.theme--catppuccin-macchiato .pagination-next.is-disabled,html.theme--catppuccin-macchiato .pagination-link[disabled],html.theme--catppuccin-macchiato .pagination-link.is-disabled{background-color:#5b6078;border-color:#5b6078;box-shadow:none;color:#f5f7fd;opacity:0.5}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-macchiato .pagination-link.is-current{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .pagination-ellipsis{color:#6e738d;pointer-events:none}html.theme--catppuccin-macchiato .pagination-list{flex-wrap:wrap}html.theme--catppuccin-macchiato .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .pagination{flex-wrap:wrap}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-macchiato .pagination-previous{order:2}html.theme--catppuccin-macchiato .pagination-next{order:3}html.theme--catppuccin-macchiato .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-macchiato .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-macchiato .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-macchiato .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-macchiato .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-macchiato .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-macchiato .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-macchiato .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-macchiato .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-macchiato .panel.is-dark .panel-heading,html.theme--catppuccin-macchiato .content kbd.panel .panel-heading{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-macchiato .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363a4f}html.theme--catppuccin-macchiato .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-macchiato .content kbd.panel .panel-block.is-active .panel-icon{color:#363a4f}html.theme--catppuccin-macchiato .panel.is-primary .panel-heading,html.theme--catppuccin-macchiato details.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-macchiato details.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-link .panel-heading{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .panel.is-link .panel-tabs a.is-active{border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-link .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-info .panel-heading{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-info .panel-tabs a.is-active{border-bottom-color:#8bd5ca}html.theme--catppuccin-macchiato .panel.is-info .panel-block.is-active .panel-icon{color:#8bd5ca}html.theme--catppuccin-macchiato .panel.is-success .panel-heading{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6da95}html.theme--catppuccin-macchiato .panel.is-success .panel-block.is-active .panel-icon{color:#a6da95}html.theme--catppuccin-macchiato .panel.is-warning .panel-heading{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#eed49f}html.theme--catppuccin-macchiato .panel.is-warning .panel-block.is-active .panel-icon{color:#eed49f}html.theme--catppuccin-macchiato .panel.is-danger .panel-heading{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#ed8796}html.theme--catppuccin-macchiato .panel.is-danger .panel-block.is-active .panel-icon{color:#ed8796}html.theme--catppuccin-macchiato .panel-tabs:not(:last-child),html.theme--catppuccin-macchiato .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-macchiato .panel-heading{background-color:#494d64;border-radius:8px 8px 0 0;color:#b5c1f1;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-macchiato .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-macchiato .panel-tabs a{border-bottom:1px solid #5b6078;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-macchiato .panel-tabs a.is-active{border-bottom-color:#494d64;color:#739df2}html.theme--catppuccin-macchiato .panel-list a{color:#cad3f5}html.theme--catppuccin-macchiato .panel-list a:hover{color:#8aadf4}html.theme--catppuccin-macchiato .panel-block{align-items:center;color:#b5c1f1;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-macchiato .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-macchiato .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-macchiato .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-macchiato .panel-block.is-active{border-left-color:#8aadf4;color:#739df2}html.theme--catppuccin-macchiato .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-macchiato a.panel-block,html.theme--catppuccin-macchiato label.panel-block{cursor:pointer}html.theme--catppuccin-macchiato a.panel-block:hover,html.theme--catppuccin-macchiato label.panel-block:hover{background-color:#1e2030}html.theme--catppuccin-macchiato .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f5f7fd;margin-right:.75em}html.theme--catppuccin-macchiato .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-macchiato .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-macchiato .tabs a{align-items:center;border-bottom-color:#5b6078;border-bottom-style:solid;border-bottom-width:1px;color:#cad3f5;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-macchiato .tabs a:hover{border-bottom-color:#b5c1f1;color:#b5c1f1}html.theme--catppuccin-macchiato .tabs li{display:block}html.theme--catppuccin-macchiato .tabs li.is-active a{border-bottom-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .tabs ul{align-items:center;border-bottom-color:#5b6078;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-macchiato .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-macchiato .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-macchiato .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-macchiato .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-macchiato .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-macchiato .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-macchiato .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-macchiato .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-macchiato .tabs.is-boxed a:hover{background-color:#1e2030;border-bottom-color:#5b6078}html.theme--catppuccin-macchiato .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5b6078;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-macchiato .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .tabs.is-toggle a{border-color:#5b6078;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-macchiato .tabs.is-toggle a:hover{background-color:#1e2030;border-color:#6e738d;z-index:2}html.theme--catppuccin-macchiato .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-macchiato .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-macchiato .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-macchiato .tabs.is-toggle li.is-active a{background-color:#8aadf4;border-color:#8aadf4;color:#fff;z-index:1}html.theme--catppuccin-macchiato .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-macchiato .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-macchiato .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-macchiato .tabs.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-macchiato .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .column.is-narrow,html.theme--catppuccin-macchiato .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full,html.theme--catppuccin-macchiato .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters,html.theme--catppuccin-macchiato .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds,html.theme--catppuccin-macchiato .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half,html.theme--catppuccin-macchiato .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third,html.theme--catppuccin-macchiato .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter,html.theme--catppuccin-macchiato .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth,html.theme--catppuccin-macchiato .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths,html.theme--catppuccin-macchiato .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths,html.theme--catppuccin-macchiato .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths,html.theme--catppuccin-macchiato .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters,html.theme--catppuccin-macchiato .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds,html.theme--catppuccin-macchiato .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half,html.theme--catppuccin-macchiato .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third,html.theme--catppuccin-macchiato .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter,html.theme--catppuccin-macchiato .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth,html.theme--catppuccin-macchiato .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths,html.theme--catppuccin-macchiato .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths,html.theme--catppuccin-macchiato .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths,html.theme--catppuccin-macchiato .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0,html.theme--catppuccin-macchiato .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0,html.theme--catppuccin-macchiato .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1,html.theme--catppuccin-macchiato .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1,html.theme--catppuccin-macchiato .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2,html.theme--catppuccin-macchiato .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2,html.theme--catppuccin-macchiato .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3,html.theme--catppuccin-macchiato .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3,html.theme--catppuccin-macchiato .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4,html.theme--catppuccin-macchiato .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4,html.theme--catppuccin-macchiato .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5,html.theme--catppuccin-macchiato .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5,html.theme--catppuccin-macchiato .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6,html.theme--catppuccin-macchiato .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6,html.theme--catppuccin-macchiato .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7,html.theme--catppuccin-macchiato .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7,html.theme--catppuccin-macchiato .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8,html.theme--catppuccin-macchiato .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8,html.theme--catppuccin-macchiato .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9,html.theme--catppuccin-macchiato .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9,html.theme--catppuccin-macchiato .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10,html.theme--catppuccin-macchiato .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10,html.theme--catppuccin-macchiato .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11,html.theme--catppuccin-macchiato .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11,html.theme--catppuccin-macchiato .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12,html.theme--catppuccin-macchiato .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12,html.theme--catppuccin-macchiato .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-macchiato .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-macchiato .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-macchiato .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-macchiato .columns.is-centered{justify-content:center}html.theme--catppuccin-macchiato .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-macchiato .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-macchiato .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-macchiato .columns.is-mobile{display:flex}html.theme--catppuccin-macchiato .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-macchiato .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-desktop{display:flex}}html.theme--catppuccin-macchiato .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-macchiato .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-macchiato .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-macchiato .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-macchiato .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-macchiato .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-macchiato .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-macchiato .tile.is-child{margin:0 !important}html.theme--catppuccin-macchiato .tile.is-parent{padding:.75rem}html.theme--catppuccin-macchiato .tile.is-vertical{flex-direction:column}html.theme--catppuccin-macchiato .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .tile:not(.is-child){display:flex}html.theme--catppuccin-macchiato .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .tile.is-3{flex:none;width:25%}html.theme--catppuccin-macchiato .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .tile.is-6{flex:none;width:50%}html.theme--catppuccin-macchiato .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .tile.is-9{flex:none;width:75%}html.theme--catppuccin-macchiato .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-macchiato .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-macchiato .hero .navbar{background:none}html.theme--catppuccin-macchiato .hero .tabs ul{border-bottom:none}html.theme--catppuccin-macchiato .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-white strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-macchiato .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-macchiato .hero.is-white .navbar-item,html.theme--catppuccin-macchiato .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-macchiato .hero.is-white a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-white .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-macchiato .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-black strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-black .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-macchiato .hero.is-black .navbar-item,html.theme--catppuccin-macchiato .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-black a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-black .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-macchiato .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-light strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-macchiato .hero.is-light .navbar-item,html.theme--catppuccin-macchiato .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-light .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-macchiato .hero.is-dark,html.theme--catppuccin-macchiato .content kbd.hero{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-dark strong,html.theme--catppuccin-macchiato .content kbd.hero strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-dark .title,html.theme--catppuccin-macchiato .content kbd.hero .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .subtitle,html.theme--catppuccin-macchiato .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-macchiato .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-dark .subtitle strong,html.theme--catppuccin-macchiato .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-dark .navbar-menu,html.theme--catppuccin-macchiato .content kbd.hero .navbar-menu{background-color:#363a4f}}html.theme--catppuccin-macchiato .hero.is-dark .navbar-item,html.theme--catppuccin-macchiato .content kbd.hero .navbar-item,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .tabs a,html.theme--catppuccin-macchiato .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-dark .tabs a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs li.is-active a{color:#363a4f !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .hero.is-dark.is-bold,html.theme--catppuccin-macchiato .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1d2535 0%, #363a4f 71%, #3d3c62 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-macchiato .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1d2535 0%, #363a4f 71%, #3d3c62 100%)}}html.theme--catppuccin-macchiato .hero.is-primary,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-primary strong,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-primary .title,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .subtitle,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-primary .subtitle strong,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-primary .navbar-menu,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#8aadf4}}html.theme--catppuccin-macchiato .hero.is-primary .navbar-item,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link:hover,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .tabs a,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-primary .tabs a:hover,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#8aadf4 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .hero.is-primary.is-bold,html.theme--catppuccin-macchiato details.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-macchiato details.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}}html.theme--catppuccin-macchiato .hero.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-link strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-link .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-link .navbar-menu{background-color:#8aadf4}}html.theme--catppuccin-macchiato .hero.is-link .navbar-item,html.theme--catppuccin-macchiato .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-link a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-link .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-link .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-link .tabs li.is-active a{color:#8aadf4 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .hero.is-link.is-bold{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}}html.theme--catppuccin-macchiato .hero.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-info strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-info .navbar-menu{background-color:#8bd5ca}}html.theme--catppuccin-macchiato .hero.is-info .navbar-item,html.theme--catppuccin-macchiato .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-info .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-info .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-info .tabs li.is-active a{color:#8bd5ca !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .hero.is-info.is-bold{background-image:linear-gradient(141deg, #5bd2ac 0%, #8bd5ca 71%, #9adedf 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #5bd2ac 0%, #8bd5ca 71%, #9adedf 100%)}}html.theme--catppuccin-macchiato .hero.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-success strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-success .navbar-menu{background-color:#a6da95}}html.theme--catppuccin-macchiato .hero.is-success .navbar-item,html.theme--catppuccin-macchiato .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-success .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-success .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-success .tabs li.is-active a{color:#a6da95 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .hero.is-success.is-bold{background-image:linear-gradient(141deg, #94d765 0%, #a6da95 71%, #aae4a5 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #94d765 0%, #a6da95 71%, #aae4a5 100%)}}html.theme--catppuccin-macchiato .hero.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-warning strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-warning .navbar-menu{background-color:#eed49f}}html.theme--catppuccin-macchiato .hero.is-warning .navbar-item,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-warning .tabs li.is-active a{color:#eed49f !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #efae6b 0%, #eed49f 71%, #f4e9b2 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #efae6b 0%, #eed49f 71%, #f4e9b2 100%)}}html.theme--catppuccin-macchiato .hero.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-danger strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-danger .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-danger .navbar-menu{background-color:#ed8796}}html.theme--catppuccin-macchiato .hero.is-danger .navbar-item,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-danger .tabs li.is-active a{color:#ed8796 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #f05183 0%, #ed8796 71%, #f39c9a 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f05183 0%, #ed8796 71%, #f39c9a 100%)}}html.theme--catppuccin-macchiato .hero.is-small .hero-body,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-macchiato .hero.is-halfheight .hero-body,html.theme--catppuccin-macchiato .hero.is-fullheight .hero-body,html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-macchiato .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-macchiato .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-macchiato .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-macchiato .hero-video{overflow:hidden}html.theme--catppuccin-macchiato .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-macchiato .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero-video{display:none}}html.theme--catppuccin-macchiato .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero-buttons .button{display:flex}html.theme--catppuccin-macchiato .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-macchiato .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-macchiato .hero-head,html.theme--catppuccin-macchiato .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero-body{padding:3rem 3rem}}html.theme--catppuccin-macchiato .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .section{padding:3rem 3rem}html.theme--catppuccin-macchiato .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-macchiato .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-macchiato .footer{background-color:#1e2030;padding:3rem 1.5rem 6rem}html.theme--catppuccin-macchiato h1 .docs-heading-anchor,html.theme--catppuccin-macchiato h1 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h1 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h2 .docs-heading-anchor,html.theme--catppuccin-macchiato h2 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h2 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h3 .docs-heading-anchor,html.theme--catppuccin-macchiato h3 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h3 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h4 .docs-heading-anchor,html.theme--catppuccin-macchiato h4 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h4 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h5 .docs-heading-anchor,html.theme--catppuccin-macchiato h5 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h5 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h6 .docs-heading-anchor,html.theme--catppuccin-macchiato h6 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h6 .docs-heading-anchor:visited{color:#cad3f5}html.theme--catppuccin-macchiato h1 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h2 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h3 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h4 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h5 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-macchiato h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-macchiato h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-macchiato .docs-light-only{display:none !important}html.theme--catppuccin-macchiato pre{position:relative;overflow:hidden}html.theme--catppuccin-macchiato pre code,html.theme--catppuccin-macchiato pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-macchiato pre code:first-of-type,html.theme--catppuccin-macchiato pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-macchiato pre code:last-of-type,html.theme--catppuccin-macchiato pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-macchiato pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#cad3f5;cursor:pointer;text-align:center}html.theme--catppuccin-macchiato pre .copy-button:focus,html.theme--catppuccin-macchiato pre .copy-button:hover{opacity:1;background:rgba(202,211,245,0.1);color:#8aadf4}html.theme--catppuccin-macchiato pre .copy-button.success{color:#a6da95;opacity:1}html.theme--catppuccin-macchiato pre .copy-button.error{color:#ed8796;opacity:1}html.theme--catppuccin-macchiato pre:hover .copy-button{opacity:1}html.theme--catppuccin-macchiato .link-icon:hover{color:#8aadf4}html.theme--catppuccin-macchiato .admonition{background-color:#1e2030;border-style:solid;border-width:2px;border-color:#b8c0e0;border-radius:4px;font-size:1rem}html.theme--catppuccin-macchiato .admonition strong{color:currentColor}html.theme--catppuccin-macchiato .admonition.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-macchiato .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .admonition.is-default{background-color:#1e2030;border-color:#b8c0e0}html.theme--catppuccin-macchiato .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#b8c0e0}html.theme--catppuccin-macchiato .admonition.is-default>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-info{background-color:#1e2030;border-color:#8bd5ca}html.theme--catppuccin-macchiato .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#8bd5ca}html.theme--catppuccin-macchiato .admonition.is-info>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-success{background-color:#1e2030;border-color:#a6da95}html.theme--catppuccin-macchiato .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6da95}html.theme--catppuccin-macchiato .admonition.is-success>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-warning{background-color:#1e2030;border-color:#eed49f}html.theme--catppuccin-macchiato .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#eed49f}html.theme--catppuccin-macchiato .admonition.is-warning>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-danger{background-color:#1e2030;border-color:#ed8796}html.theme--catppuccin-macchiato .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#ed8796}html.theme--catppuccin-macchiato .admonition.is-danger>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-compat{background-color:#1e2030;border-color:#91d7e3}html.theme--catppuccin-macchiato .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#91d7e3}html.theme--catppuccin-macchiato .admonition.is-compat>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-todo{background-color:#1e2030;border-color:#c6a0f6}html.theme--catppuccin-macchiato .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#c6a0f6}html.theme--catppuccin-macchiato .admonition.is-todo>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition-header{color:#b8c0e0;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-macchiato .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-macchiato .admonition-header .admonition-anchor{opacity:0;margin-left:0.5em;font-size:0.75em;color:inherit;text-decoration:none;transition:opacity 0.2s ease-in-out}html.theme--catppuccin-macchiato .admonition-header .admonition-anchor:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-macchiato .admonition-header .admonition-anchor:hover{opacity:1 !important;text-decoration:none}html.theme--catppuccin-macchiato .admonition-header:hover .admonition-anchor{opacity:0.8}html.theme--catppuccin-macchiato details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-macchiato details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-macchiato details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-macchiato .admonition-body{color:#cad3f5;padding:0.5rem .75rem}html.theme--catppuccin-macchiato .admonition-body pre{background-color:#1e2030}html.theme--catppuccin-macchiato .admonition-body code{background-color:#1e2030}html.theme--catppuccin-macchiato details.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #5b6078;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-macchiato details.docstring>summary{list-style-type:none;align-items:stretch;padding:0.5rem .75rem;background-color:#1e2030;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5b6078;overflow:auto}html.theme--catppuccin-macchiato details.docstring>summary code{background-color:transparent}html.theme--catppuccin-macchiato details.docstring>summary .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-macchiato details.docstring>summary .docstring-binding{margin-right:0.3em}html.theme--catppuccin-macchiato details.docstring>summary .docstring-category{margin-left:0.3em}html.theme--catppuccin-macchiato details.docstring>summary::before{content:'\f054';font-family:"Font Awesome 6 Free";font-weight:900;min-width:1.1rem;color:#2E63BD;display:inline-block}html.theme--catppuccin-macchiato details.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5b6078}html.theme--catppuccin-macchiato details.docstring>section:last-child{border-bottom:none}html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-macchiato details.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-macchiato details.docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-macchiato details.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-macchiato details.docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-macchiato details.docstring[open]>summary::before{content:"\f078"}html.theme--catppuccin-macchiato .documenter-example-output{background-color:#24273a}html.theme--catppuccin-macchiato .warning-overlay-base,html.theme--catppuccin-macchiato .dev-warning-overlay,html.theme--catppuccin-macchiato .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-macchiato .warning-overlay-base .outdated-warning-closer,html.theme--catppuccin-macchiato .dev-warning-overlay .outdated-warning-closer,html.theme--catppuccin-macchiato .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-macchiato .warning-overlay-base a,html.theme--catppuccin-macchiato .dev-warning-overlay a,html.theme--catppuccin-macchiato .outdated-warning-overlay a{color:#8aadf4}html.theme--catppuccin-macchiato .warning-overlay-base a:hover,html.theme--catppuccin-macchiato .dev-warning-overlay a:hover,html.theme--catppuccin-macchiato .outdated-warning-overlay a:hover{color:#91d7e3}html.theme--catppuccin-macchiato .outdated-warning-overlay{background-color:#1e2030;color:#cad3f5;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-macchiato .dev-warning-overlay{background-color:#1e2030;color:#cad3f5;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-macchiato .footnote-reference{position:relative;display:inline-block}html.theme--catppuccin-macchiato .footnote-preview{display:none;position:absolute;z-index:1000;max-width:300px;width:max-content;background-color:#24273a;border:1px solid #91d7e3;padding:10px;border-radius:5px;top:calc(100% + 10px);left:50%;transform:translateX(-50%);box-sizing:border-box;--arrow-left: 50%}html.theme--catppuccin-macchiato .footnote-preview::before{content:"";position:absolute;top:-10px;left:var(--arrow-left);transform:translateX(-50%);border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #91d7e3}html.theme--catppuccin-macchiato .content pre{border:2px solid #5b6078;border-radius:4px}html.theme--catppuccin-macchiato .content code{font-weight:inherit}html.theme--catppuccin-macchiato .content a code{color:#8aadf4}html.theme--catppuccin-macchiato .content a:hover code{color:#91d7e3}html.theme--catppuccin-macchiato .content h1 code,html.theme--catppuccin-macchiato .content h2 code,html.theme--catppuccin-macchiato .content h3 code,html.theme--catppuccin-macchiato .content h4 code,html.theme--catppuccin-macchiato .content h5 code,html.theme--catppuccin-macchiato .content h6 code{color:#cad3f5}html.theme--catppuccin-macchiato .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-macchiato .content blockquote>ul:first-child,html.theme--catppuccin-macchiato .content blockquote>ol:first-child,html.theme--catppuccin-macchiato .content .admonition-body>ul:first-child,html.theme--catppuccin-macchiato .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-macchiato pre,html.theme--catppuccin-macchiato code{font-variant-ligatures:no-contextual}html.theme--catppuccin-macchiato .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-macchiato .breadcrumb a.is-disabled,html.theme--catppuccin-macchiato .breadcrumb a.is-disabled:hover{color:#b5c1f1}html.theme--catppuccin-macchiato .hljs{background:initial !important}html.theme--catppuccin-macchiato .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-macchiato .katex-display,html.theme--catppuccin-macchiato mjx-container,html.theme--catppuccin-macchiato .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-macchiato html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-macchiato li.no-marker{list-style:none}html.theme--catppuccin-macchiato #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-macchiato #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main{width:100%}html.theme--catppuccin-macchiato #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-macchiato #documenter .docs-main>header,html.theme--catppuccin-macchiato #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar{background-color:#24273a;border-bottom:1px solid #5b6078;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow:hidden}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes{border-top:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li details.docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-macchiato .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5b6078;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-macchiato #documenter .docs-sidebar{display:flex;flex-direction:column;color:#cad3f5;background-color:#1e2030;border-right:1px solid #5b6078;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-macchiato #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name a:hover{color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5b6078;display:none;padding:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5b6078;padding-bottom:1.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#cad3f5;background:#1e2030}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#cad3f5;background-color:#26283d}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5b6078;border-bottom:1px solid #5b6078;background-color:#181926}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#181926;color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#26283d;color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"โšฌ";margin-right:0.4em}html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#2e3149}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#3d4162}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#2e3149}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#3d4162}}html.theme--catppuccin-macchiato kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-macchiato .search-min-width-50{min-width:50%}html.theme--catppuccin-macchiato .search-min-height-100{min-height:100%}html.theme--catppuccin-macchiato .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-macchiato .search-result-link{border-radius:0.7em;transition:all 300ms;border:1px solid transparent}html.theme--catppuccin-macchiato .search-result-link:hover,html.theme--catppuccin-macchiato .search-result-link:focus{background-color:rgba(0,128,128,0.1);outline:none;border-color:#8bd5ca}html.theme--catppuccin-macchiato .search-result-link .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-macchiato .property-search-result-badge,html.theme--catppuccin-macchiato .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-macchiato .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:hover .search-filter,html.theme--catppuccin-macchiato .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-macchiato .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-macchiato .search-filter:hover,html.theme--catppuccin-macchiato .search-filter:focus{color:#333}html.theme--catppuccin-macchiato .search-filter-selected{color:#363a4f;background-color:#b7bdf8}html.theme--catppuccin-macchiato .search-filter-selected:hover,html.theme--catppuccin-macchiato .search-filter-selected:focus{color:#363a4f}html.theme--catppuccin-macchiato .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-macchiato .search-divider{border-bottom:1px solid #5b6078}html.theme--catppuccin-macchiato .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-macchiato .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-macchiato .w-100{width:100%}html.theme--catppuccin-macchiato .gap-2{gap:0.5rem}html.theme--catppuccin-macchiato .gap-4{gap:1rem}html.theme--catppuccin-macchiato .gap-8{gap:2rem}html.theme--catppuccin-macchiato{background-color:#24273a;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-macchiato a{transition:all 200ms ease}html.theme--catppuccin-macchiato .label{color:#cad3f5}html.theme--catppuccin-macchiato .button,html.theme--catppuccin-macchiato .control.has-icons-left .icon,html.theme--catppuccin-macchiato .control.has-icons-right .icon,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .select,html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea{height:2.5em;color:#cad3f5}html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#cad3f5}html.theme--catppuccin-macchiato .select:after,html.theme--catppuccin-macchiato .select select{border-width:1px}html.theme--catppuccin-macchiato .menu-list a{transition:all 300ms ease}html.theme--catppuccin-macchiato .modal-card-foot,html.theme--catppuccin-macchiato .modal-card-head{border-color:#5b6078}html.theme--catppuccin-macchiato .navbar{border-radius:.4em}html.theme--catppuccin-macchiato .navbar.is-transparent{background:none}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .navbar .navbar-menu{background-color:#8aadf4;border-radius:0 0 .4em .4em}}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){color:#363a4f}html.theme--catppuccin-macchiato .tag.is-link:not(body),html.theme--catppuccin-macchiato details.docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-macchiato .content kbd.is-link:not(body){color:#363a4f}html.theme--catppuccin-macchiato .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-macchiato .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-macchiato .ansi span.sgr3{font-style:italic}html.theme--catppuccin-macchiato .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-macchiato .ansi span.sgr7{color:#24273a;background-color:#cad3f5}html.theme--catppuccin-macchiato .ansi span.sgr8{color:transparent}html.theme--catppuccin-macchiato .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-macchiato .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-macchiato .ansi span.sgr30{color:#494d64}html.theme--catppuccin-macchiato .ansi span.sgr31{color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr32{color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr33{color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr34{color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr35{color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr36{color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr37{color:#b8c0e0}html.theme--catppuccin-macchiato .ansi span.sgr40{background-color:#494d64}html.theme--catppuccin-macchiato .ansi span.sgr41{background-color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr42{background-color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr43{background-color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr44{background-color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr45{background-color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr46{background-color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr47{background-color:#b8c0e0}html.theme--catppuccin-macchiato .ansi span.sgr90{color:#5b6078}html.theme--catppuccin-macchiato .ansi span.sgr91{color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr92{color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr93{color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr94{color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr95{color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr96{color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr97{color:#a5adcb}html.theme--catppuccin-macchiato .ansi span.sgr100{background-color:#5b6078}html.theme--catppuccin-macchiato .ansi span.sgr101{background-color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr102{background-color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr103{background-color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr104{background-color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr105{background-color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr106{background-color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr107{background-color:#a5adcb}html.theme--catppuccin-macchiato code.language-julia-repl>span.hljs-meta{color:#a6da95;font-weight:bolder}html.theme--catppuccin-macchiato code .hljs{color:#cad3f5;background:#24273a}html.theme--catppuccin-macchiato code .hljs-keyword{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-built_in{color:#ed8796}html.theme--catppuccin-macchiato code .hljs-type{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-literal{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-number{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-operator{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-punctuation{color:#b8c0e0}html.theme--catppuccin-macchiato code .hljs-property{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-regexp{color:#f5bde6}html.theme--catppuccin-macchiato code .hljs-string{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-char.escape_{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-subst{color:#a5adcb}html.theme--catppuccin-macchiato code .hljs-symbol{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-variable{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-variable.language_{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-variable.constant_{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-title{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-title.class_{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-title.function_{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-params{color:#cad3f5}html.theme--catppuccin-macchiato code .hljs-comment{color:#5b6078}html.theme--catppuccin-macchiato code .hljs-doctag{color:#ed8796}html.theme--catppuccin-macchiato code .hljs-meta{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-section{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-tag{color:#a5adcb}html.theme--catppuccin-macchiato code .hljs-name{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-attr{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-attribute{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-bullet{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-code{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-emphasis{color:#ed8796;font-style:italic}html.theme--catppuccin-macchiato code .hljs-strong{color:#ed8796;font-weight:bold}html.theme--catppuccin-macchiato code .hljs-formula{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-link{color:#7dc4e4;font-style:italic}html.theme--catppuccin-macchiato code .hljs-quote{color:#a6da95;font-style:italic}html.theme--catppuccin-macchiato code .hljs-selector-tag{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-selector-id{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-selector-class{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-selector-attr{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-selector-pseudo{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-template-tag{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-template-variable{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-addition{color:#a6da95;background:rgba(166,227,161,0.15)}html.theme--catppuccin-macchiato code .hljs-deletion{color:#ed8796;background:rgba(243,139,168,0.15)}html.theme--catppuccin-macchiato .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover,html.theme--catppuccin-macchiato .search-result-link:focus{background-color:#363a4f}html.theme--catppuccin-macchiato .search-result-link .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:hover .search-filter,html.theme--catppuccin-macchiato .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:focus .search-filter{color:#363a4f !important;background-color:#b7bdf8 !important}html.theme--catppuccin-macchiato .search-result-title{color:#cad3f5}html.theme--catppuccin-macchiato .search-result-highlight{background-color:#ed8796;color:#1e2030}html.theme--catppuccin-macchiato .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-macchiato .w-100{width:100%}html.theme--catppuccin-macchiato .gap-2{gap:0.5rem}html.theme--catppuccin-macchiato .gap-4{gap:1rem} diff --git a/save/docs/build/assets/themes/catppuccin-mocha.css b/save/docs/build/assets/themes/catppuccin-mocha.css new file mode 100644 index 00000000..8ade7cd1 --- /dev/null +++ b/save/docs/build/assets/themes/catppuccin-mocha.css @@ -0,0 +1 @@ +๏ปฟhtml.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha .file-name,html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-mocha .pagination-previous:focus,html.theme--catppuccin-mocha .pagination-next:focus,html.theme--catppuccin-mocha .pagination-link:focus,html.theme--catppuccin-mocha .pagination-ellipsis:focus,html.theme--catppuccin-mocha .file-cta:focus,html.theme--catppuccin-mocha .file-name:focus,html.theme--catppuccin-mocha .select select:focus,html.theme--catppuccin-mocha .textarea:focus,html.theme--catppuccin-mocha .input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-mocha .button:focus,html.theme--catppuccin-mocha .is-focused.pagination-previous,html.theme--catppuccin-mocha .is-focused.pagination-next,html.theme--catppuccin-mocha .is-focused.pagination-link,html.theme--catppuccin-mocha .is-focused.pagination-ellipsis,html.theme--catppuccin-mocha .is-focused.file-cta,html.theme--catppuccin-mocha .is-focused.file-name,html.theme--catppuccin-mocha .select select.is-focused,html.theme--catppuccin-mocha .is-focused.textarea,html.theme--catppuccin-mocha .is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-focused.button,html.theme--catppuccin-mocha .pagination-previous:active,html.theme--catppuccin-mocha .pagination-next:active,html.theme--catppuccin-mocha .pagination-link:active,html.theme--catppuccin-mocha .pagination-ellipsis:active,html.theme--catppuccin-mocha .file-cta:active,html.theme--catppuccin-mocha .file-name:active,html.theme--catppuccin-mocha .select select:active,html.theme--catppuccin-mocha .textarea:active,html.theme--catppuccin-mocha .input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-mocha .button:active,html.theme--catppuccin-mocha .is-active.pagination-previous,html.theme--catppuccin-mocha .is-active.pagination-next,html.theme--catppuccin-mocha .is-active.pagination-link,html.theme--catppuccin-mocha .is-active.pagination-ellipsis,html.theme--catppuccin-mocha .is-active.file-cta,html.theme--catppuccin-mocha .is-active.file-name,html.theme--catppuccin-mocha .select select.is-active,html.theme--catppuccin-mocha .is-active.textarea,html.theme--catppuccin-mocha .is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .is-active.button{outline:none}html.theme--catppuccin-mocha .pagination-previous[disabled],html.theme--catppuccin-mocha .pagination-next[disabled],html.theme--catppuccin-mocha .pagination-link[disabled],html.theme--catppuccin-mocha .pagination-ellipsis[disabled],html.theme--catppuccin-mocha .file-cta[disabled],html.theme--catppuccin-mocha .file-name[disabled],html.theme--catppuccin-mocha .select select[disabled],html.theme--catppuccin-mocha .textarea[disabled],html.theme--catppuccin-mocha .input[disabled],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-mocha .button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-mocha .file-name,html.theme--catppuccin-mocha fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-mocha .select select,fieldset[disabled] html.theme--catppuccin-mocha .textarea,fieldset[disabled] html.theme--catppuccin-mocha .input,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha fieldset[disabled] .select select,html.theme--catppuccin-mocha .select fieldset[disabled] select,html.theme--catppuccin-mocha fieldset[disabled] .textarea,html.theme--catppuccin-mocha fieldset[disabled] .input,html.theme--catppuccin-mocha fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-mocha .button,html.theme--catppuccin-mocha fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-mocha .tabs,html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .breadcrumb,html.theme--catppuccin-mocha .file,html.theme--catppuccin-mocha .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-mocha .admonition:not(:last-child),html.theme--catppuccin-mocha .tabs:not(:last-child),html.theme--catppuccin-mocha .pagination:not(:last-child),html.theme--catppuccin-mocha .message:not(:last-child),html.theme--catppuccin-mocha .level:not(:last-child),html.theme--catppuccin-mocha .breadcrumb:not(:last-child),html.theme--catppuccin-mocha .block:not(:last-child),html.theme--catppuccin-mocha .title:not(:last-child),html.theme--catppuccin-mocha .subtitle:not(:last-child),html.theme--catppuccin-mocha .table-container:not(:last-child),html.theme--catppuccin-mocha .table:not(:last-child),html.theme--catppuccin-mocha .progress:not(:last-child),html.theme--catppuccin-mocha .notification:not(:last-child),html.theme--catppuccin-mocha .content:not(:last-child),html.theme--catppuccin-mocha .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .modal-close,html.theme--catppuccin-mocha .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-mocha .modal-close::before,html.theme--catppuccin-mocha .delete::before,html.theme--catppuccin-mocha .modal-close::after,html.theme--catppuccin-mocha .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-mocha .modal-close::before,html.theme--catppuccin-mocha .delete::before{height:2px;width:50%}html.theme--catppuccin-mocha .modal-close::after,html.theme--catppuccin-mocha .delete::after{height:50%;width:2px}html.theme--catppuccin-mocha .modal-close:hover,html.theme--catppuccin-mocha .delete:hover,html.theme--catppuccin-mocha .modal-close:focus,html.theme--catppuccin-mocha .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-mocha .modal-close:active,html.theme--catppuccin-mocha .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-mocha .is-small.modal-close,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-mocha .is-small.delete,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-mocha .is-medium.modal-close,html.theme--catppuccin-mocha .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-mocha .is-large.modal-close,html.theme--catppuccin-mocha .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-mocha .control.is-loading::after,html.theme--catppuccin-mocha .select.is-loading::after,html.theme--catppuccin-mocha .loader,html.theme--catppuccin-mocha .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #7f849c;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-mocha .hero-video,html.theme--catppuccin-mocha .modal-background,html.theme--catppuccin-mocha .modal,html.theme--catppuccin-mocha .image.is-square img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-mocha .image.is-square .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-mocha .image.is-1by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-mocha .image.is-1by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-mocha .image.is-5by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-mocha .image.is-5by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-mocha .image.is-4by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-mocha .image.is-4by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-mocha .image.is-3by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-mocha .image.is-5by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-mocha .image.is-5by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-mocha .image.is-16by9 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-mocha .image.is-16by9 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-mocha .image.is-2by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-mocha .image.is-2by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-mocha .image.is-3by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-mocha .image.is-3by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-mocha .image.is-4by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-mocha .image.is-4by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-mocha .image.is-3by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-mocha .image.is-3by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-mocha .image.is-2by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-mocha .image.is-2by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-mocha .image.is-3by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-mocha .image.is-9by16 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-mocha .image.is-9by16 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-mocha .image.is-1by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-mocha .image.is-1by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-mocha .image.is-1by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-mocha .image.is-1by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-mocha .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#313244 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c26 !important}.has-background-dark{background-color:#313244 !important}.has-text-primary{color:#89b4fa !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#5895f8 !important}.has-background-primary{background-color:#89b4fa !important}.has-text-primary-light{color:#ebf3fe !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bbd3fc !important}.has-background-primary-light{background-color:#ebf3fe !important}.has-text-primary-dark{color:#063c93 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#0850c4 !important}.has-background-primary-dark{background-color:#063c93 !important}.has-text-link{color:#89b4fa !important}a.has-text-link:hover,a.has-text-link:focus{color:#5895f8 !important}.has-background-link{background-color:#89b4fa !important}.has-text-link-light{color:#ebf3fe !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bbd3fc !important}.has-background-link-light{background-color:#ebf3fe !important}.has-text-link-dark{color:#063c93 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#0850c4 !important}.has-background-link-dark{background-color:#063c93 !important}.has-text-info{color:#94e2d5 !important}a.has-text-info:hover,a.has-text-info:focus{color:#6cd7c5 !important}.has-background-info{background-color:#94e2d5 !important}.has-text-info-light{color:#effbf9 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c7f0e9 !important}.has-background-info-light{background-color:#effbf9 !important}.has-text-info-dark{color:#207466 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#2a9c89 !important}.has-background-info-dark{background-color:#207466 !important}.has-text-success{color:#a6e3a1 !important}a.has-text-success:hover,a.has-text-success:focus{color:#81d77a !important}.has-background-success{background-color:#a6e3a1 !important}.has-text-success-light{color:#f0faef !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#cbefc8 !important}.has-background-success-light{background-color:#f0faef !important}.has-text-success-dark{color:#287222 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#36992e !important}.has-background-success-dark{background-color:#287222 !important}.has-text-warning{color:#f9e2af !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#f5d180 !important}.has-background-warning{background-color:#f9e2af !important}.has-text-warning-light{color:#fef8ec !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fae7bd !important}.has-background-warning-light{background-color:#fef8ec !important}.has-text-warning-dark{color:#8a620a !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#b9840e !important}.has-background-warning-dark{background-color:#8a620a !important}.has-text-danger{color:#f38ba8 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ee5d85 !important}.has-background-danger{background-color:#f38ba8 !important}.has-text-danger-light{color:#fdedf1 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f8bece !important}.has-background-danger-light{background-color:#fdedf1 !important}.has-text-danger-dark{color:#991036 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c71546 !important}.has-background-danger-dark{background-color:#991036 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#313244 !important}.has-background-grey-darker{background-color:#313244 !important}.has-text-grey-dark{color:#45475a !important}.has-background-grey-dark{background-color:#45475a !important}.has-text-grey{color:#585b70 !important}.has-background-grey{background-color:#585b70 !important}.has-text-grey-light{color:#6c7086 !important}.has-background-grey-light{background-color:#6c7086 !important}.has-text-grey-lighter{color:#7f849c !important}.has-background-grey-lighter{background-color:#7f849c !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-mocha html{background-color:#1e1e2e;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-mocha article,html.theme--catppuccin-mocha aside,html.theme--catppuccin-mocha figure,html.theme--catppuccin-mocha footer,html.theme--catppuccin-mocha header,html.theme--catppuccin-mocha hgroup,html.theme--catppuccin-mocha section{display:block}html.theme--catppuccin-mocha body,html.theme--catppuccin-mocha button,html.theme--catppuccin-mocha input,html.theme--catppuccin-mocha optgroup,html.theme--catppuccin-mocha select,html.theme--catppuccin-mocha textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-mocha code,html.theme--catppuccin-mocha pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-mocha body{color:#cdd6f4;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-mocha a{color:#89b4fa;cursor:pointer;text-decoration:none}html.theme--catppuccin-mocha a strong{color:currentColor}html.theme--catppuccin-mocha a:hover{color:#89dceb}html.theme--catppuccin-mocha code{background-color:#181825;color:#cdd6f4;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-mocha hr{background-color:#181825;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-mocha img{height:auto;max-width:100%}html.theme--catppuccin-mocha input[type="checkbox"],html.theme--catppuccin-mocha input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-mocha small{font-size:.875em}html.theme--catppuccin-mocha span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-mocha strong{color:#b8c5ef;font-weight:700}html.theme--catppuccin-mocha fieldset{border:none}html.theme--catppuccin-mocha pre{-webkit-overflow-scrolling:touch;background-color:#181825;color:#cdd6f4;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-mocha pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-mocha table td,html.theme--catppuccin-mocha table th{vertical-align:top}html.theme--catppuccin-mocha table td:not([align]),html.theme--catppuccin-mocha table th:not([align]){text-align:inherit}html.theme--catppuccin-mocha table th{color:#b8c5ef}html.theme--catppuccin-mocha .box{background-color:#45475a;border-radius:8px;box-shadow:none;color:#cdd6f4;display:block;padding:1.25rem}html.theme--catppuccin-mocha a.box:hover,html.theme--catppuccin-mocha a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #89b4fa}html.theme--catppuccin-mocha a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #89b4fa}html.theme--catppuccin-mocha .button{background-color:#181825;border-color:#363653;border-width:1px;color:#89b4fa;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-mocha .button strong{color:inherit}html.theme--catppuccin-mocha .button .icon,html.theme--catppuccin-mocha .button .icon.is-small,html.theme--catppuccin-mocha .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-mocha .button .icon.is-medium,html.theme--catppuccin-mocha .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-mocha .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-mocha .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-mocha .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-mocha .button:hover,html.theme--catppuccin-mocha .button.is-hovered{border-color:#6c7086;color:#b8c5ef}html.theme--catppuccin-mocha .button:focus,html.theme--catppuccin-mocha .button.is-focused{border-color:#6c7086;color:#71a4f9}html.theme--catppuccin-mocha .button:focus:not(:active),html.theme--catppuccin-mocha .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button:active,html.theme--catppuccin-mocha .button.is-active{border-color:#45475a;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text{background-color:transparent;border-color:transparent;color:#cdd6f4;text-decoration:underline}html.theme--catppuccin-mocha .button.is-text:hover,html.theme--catppuccin-mocha .button.is-text.is-hovered,html.theme--catppuccin-mocha .button.is-text:focus,html.theme--catppuccin-mocha .button.is-text.is-focused{background-color:#181825;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text:active,html.theme--catppuccin-mocha .button.is-text.is-active{background-color:#0e0e16;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-mocha .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#89b4fa;text-decoration:none}html.theme--catppuccin-mocha .button.is-ghost:hover,html.theme--catppuccin-mocha .button.is-ghost.is-hovered{color:#89b4fa;text-decoration:underline}html.theme--catppuccin-mocha .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:hover,html.theme--catppuccin-mocha .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:focus,html.theme--catppuccin-mocha .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:focus:not(:active),html.theme--catppuccin-mocha .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .button.is-white:active,html.theme--catppuccin-mocha .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-mocha .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted:hover,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-mocha .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-outlined:hover,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-white.is-outlined:focus,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:hover,html.theme--catppuccin-mocha .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:focus,html.theme--catppuccin-mocha .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:focus:not(:active),html.theme--catppuccin-mocha .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .button.is-black:active,html.theme--catppuccin-mocha .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-mocha .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted:hover,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-outlined:hover,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-black.is-outlined:focus,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:hover,html.theme--catppuccin-mocha .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:focus,html.theme--catppuccin-mocha .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:focus:not(:active),html.theme--catppuccin-mocha .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .button.is-light:active,html.theme--catppuccin-mocha .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-mocha .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted:hover,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-outlined:hover,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-light.is-outlined:focus,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-dark,html.theme--catppuccin-mocha .content kbd.button{background-color:#313244;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:hover,html.theme--catppuccin-mocha .content kbd.button:hover,html.theme--catppuccin-mocha .button.is-dark.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-hovered{background-color:#2c2d3d;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:focus,html.theme--catppuccin-mocha .content kbd.button:focus,html.theme--catppuccin-mocha .button.is-dark.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:focus:not(:active),html.theme--catppuccin-mocha .content kbd.button:focus:not(:active),html.theme--catppuccin-mocha .button.is-dark.is-focused:not(:active),html.theme--catppuccin-mocha .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .button.is-dark:active,html.theme--catppuccin-mocha .content kbd.button:active,html.theme--catppuccin-mocha .button.is-dark.is-active,html.theme--catppuccin-mocha .content kbd.button.is-active{background-color:#262735;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark[disabled],html.theme--catppuccin-mocha .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button{background-color:#313244;border-color:#313244;box-shadow:none}html.theme--catppuccin-mocha .button.is-dark.is-inverted,html.theme--catppuccin-mocha .content kbd.button.is-inverted{background-color:#fff;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted:hover,html.theme--catppuccin-mocha .content kbd.button.is-inverted:hover,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-dark.is-inverted[disabled],html.theme--catppuccin-mocha .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-loading::after,html.theme--catppuccin-mocha .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined,html.theme--catppuccin-mocha .content kbd.button.is-outlined{background-color:transparent;border-color:#313244;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-outlined:hover,html.theme--catppuccin-mocha .content kbd.button.is-outlined:hover,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-dark.is-outlined:focus,html.theme--catppuccin-mocha .content kbd.button.is-outlined:focus,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-focused{background-color:#313244;border-color:#313244;color:#fff}html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #313244 #313244 !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined[disabled],html.theme--catppuccin-mocha .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-outlined{background-color:transparent;border-color:#313244;box-shadow:none;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #313244 #313244 !important}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:hover,html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-hovered,html.theme--catppuccin-mocha details.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:focus,html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-focused,html.theme--catppuccin-mocha details.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:focus:not(:active),html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-mocha .button.is-primary.is-focused:not(:active),html.theme--catppuccin-mocha details.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button.is-primary:active,html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-mocha .button.is-primary.is-active,html.theme--catppuccin-mocha details.docstring>section>a.button.is-active.docs-sourcelink{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary[disabled],html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary,fieldset[disabled] html.theme--catppuccin-mocha details.docstring>section>a.button.docs-sourcelink{background-color:#89b4fa;border-color:#89b4fa;box-shadow:none}html.theme--catppuccin-mocha .button.is-primary.is-inverted,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted:hover,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-primary.is-inverted[disabled],html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-loading::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-outlined:hover,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-mocha .button.is-primary.is-outlined:focus,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined[disabled],html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#89b4fa;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-light,html.theme--catppuccin-mocha details.docstring>section>a.button.is-light.docs-sourcelink{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .button.is-primary.is-light:hover,html.theme--catppuccin-mocha details.docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-light.is-hovered,html.theme--catppuccin-mocha details.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#dfebfe;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-primary.is-light:active,html.theme--catppuccin-mocha details.docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-mocha .button.is-primary.is-light.is-active,html.theme--catppuccin-mocha details.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d3e3fd;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-link{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:hover,html.theme--catppuccin-mocha .button.is-link.is-hovered{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:focus,html.theme--catppuccin-mocha .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:focus:not(:active),html.theme--catppuccin-mocha .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button.is-link:active,html.theme--catppuccin-mocha .button.is-link.is-active{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link{background-color:#89b4fa;border-color:#89b4fa;box-shadow:none}html.theme--catppuccin-mocha .button.is-link.is-inverted{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted:hover,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-link.is-outlined{background-color:transparent;border-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-outlined:hover,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-link.is-outlined:focus,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-focused{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-outlined{background-color:transparent;border-color:#89b4fa;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-light{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .button.is-link.is-light:hover,html.theme--catppuccin-mocha .button.is-link.is-light.is-hovered{background-color:#dfebfe;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-link.is-light:active,html.theme--catppuccin-mocha .button.is-link.is-light.is-active{background-color:#d3e3fd;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-info{background-color:#94e2d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:hover,html.theme--catppuccin-mocha .button.is-info.is-hovered{background-color:#8adfd1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:focus,html.theme--catppuccin-mocha .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:focus:not(:active),html.theme--catppuccin-mocha .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .button.is-info:active,html.theme--catppuccin-mocha .button.is-info.is-active{background-color:#80ddcd;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info{background-color:#94e2d5;border-color:#94e2d5;box-shadow:none}html.theme--catppuccin-mocha .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted:hover,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-info.is-outlined{background-color:transparent;border-color:#94e2d5;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-outlined:hover,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-info.is-outlined:focus,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-focused{background-color:#94e2d5;border-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #94e2d5 #94e2d5 !important}html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-outlined{background-color:transparent;border-color:#94e2d5;box-shadow:none;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #94e2d5 #94e2d5 !important}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-light{background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .button.is-info.is-light:hover,html.theme--catppuccin-mocha .button.is-info.is-light.is-hovered{background-color:#e5f8f5;border-color:transparent;color:#207466}html.theme--catppuccin-mocha .button.is-info.is-light:active,html.theme--catppuccin-mocha .button.is-info.is-light.is-active{background-color:#dbf5f1;border-color:transparent;color:#207466}html.theme--catppuccin-mocha .button.is-success{background-color:#a6e3a1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:hover,html.theme--catppuccin-mocha .button.is-success.is-hovered{background-color:#9de097;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:focus,html.theme--catppuccin-mocha .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:focus:not(:active),html.theme--catppuccin-mocha .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .button.is-success:active,html.theme--catppuccin-mocha .button.is-success.is-active{background-color:#93dd8d;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success{background-color:#a6e3a1;border-color:#a6e3a1;box-shadow:none}html.theme--catppuccin-mocha .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted:hover,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-success.is-outlined{background-color:transparent;border-color:#a6e3a1;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-outlined:hover,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-success.is-outlined:focus,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-focused{background-color:#a6e3a1;border-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6e3a1 #a6e3a1 !important}html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-outlined{background-color:transparent;border-color:#a6e3a1;box-shadow:none;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6e3a1 #a6e3a1 !important}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-light{background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .button.is-success.is-light:hover,html.theme--catppuccin-mocha .button.is-success.is-light.is-hovered{background-color:#e7f7e5;border-color:transparent;color:#287222}html.theme--catppuccin-mocha .button.is-success.is-light:active,html.theme--catppuccin-mocha .button.is-success.is-light.is-active{background-color:#def4dc;border-color:transparent;color:#287222}html.theme--catppuccin-mocha .button.is-warning{background-color:#f9e2af;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:hover,html.theme--catppuccin-mocha .button.is-warning.is-hovered{background-color:#f8dea3;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:focus,html.theme--catppuccin-mocha .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:focus:not(:active),html.theme--catppuccin-mocha .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .button.is-warning:active,html.theme--catppuccin-mocha .button.is-warning.is-active{background-color:#f7d997;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning{background-color:#f9e2af;border-color:#f9e2af;box-shadow:none}html.theme--catppuccin-mocha .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted:hover,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined{background-color:transparent;border-color:#f9e2af;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-outlined:hover,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-warning.is-outlined:focus,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-focused{background-color:#f9e2af;border-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #f9e2af #f9e2af !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-outlined{background-color:transparent;border-color:#f9e2af;box-shadow:none;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f9e2af #f9e2af !important}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-light{background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .button.is-warning.is-light:hover,html.theme--catppuccin-mocha .button.is-warning.is-light.is-hovered{background-color:#fdf4e0;border-color:transparent;color:#8a620a}html.theme--catppuccin-mocha .button.is-warning.is-light:active,html.theme--catppuccin-mocha .button.is-warning.is-light.is-active{background-color:#fcf0d4;border-color:transparent;color:#8a620a}html.theme--catppuccin-mocha .button.is-danger{background-color:#f38ba8;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:hover,html.theme--catppuccin-mocha .button.is-danger.is-hovered{background-color:#f27f9f;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:focus,html.theme--catppuccin-mocha .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:focus:not(:active),html.theme--catppuccin-mocha .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .button.is-danger:active,html.theme--catppuccin-mocha .button.is-danger.is-active{background-color:#f17497;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger{background-color:#f38ba8;border-color:#f38ba8;box-shadow:none}html.theme--catppuccin-mocha .button.is-danger.is-inverted{background-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted:hover,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined{background-color:transparent;border-color:#f38ba8;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-outlined:hover,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-danger.is-outlined:focus,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-focused{background-color:#f38ba8;border-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f38ba8 #f38ba8 !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-outlined{background-color:transparent;border-color:#f38ba8;box-shadow:none;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f38ba8 #f38ba8 !important}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-light{background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .button.is-danger.is-light:hover,html.theme--catppuccin-mocha .button.is-danger.is-light.is-hovered{background-color:#fce1e8;border-color:transparent;color:#991036}html.theme--catppuccin-mocha .button.is-danger.is-light:active,html.theme--catppuccin-mocha .button.is-danger.is-light.is-active{background-color:#fbd5e0;border-color:transparent;color:#991036}html.theme--catppuccin-mocha .button.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-mocha .button.is-small:not(.is-rounded),html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-mocha .button.is-normal{font-size:1rem}html.theme--catppuccin-mocha .button.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .button.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button{background-color:#6c7086;border-color:#585b70;box-shadow:none;opacity:.5}html.theme--catppuccin-mocha .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-mocha .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-mocha .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-mocha .button.is-static{background-color:#181825;border-color:#585b70;color:#7f849c;box-shadow:none;pointer-events:none}html.theme--catppuccin-mocha .button.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-mocha .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-mocha .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-mocha .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-mocha .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-mocha .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-mocha .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-mocha .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-mocha .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-mocha .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-mocha .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-mocha .buttons.has-addons .button:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-mocha .buttons.has-addons .button:focus,html.theme--catppuccin-mocha .buttons.has-addons .button.is-focused,html.theme--catppuccin-mocha .buttons.has-addons .button:active,html.theme--catppuccin-mocha .buttons.has-addons .button.is-active,html.theme--catppuccin-mocha .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-mocha .buttons.has-addons .button:focus:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-mocha .buttons.has-addons .button:active:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-mocha .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .buttons.is-centered{justify-content:center}html.theme--catppuccin-mocha .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-mocha .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .button.is-responsive.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-mocha .button.is-responsive,html.theme--catppuccin-mocha .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-mocha .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-mocha .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .button.is-responsive.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-mocha .button.is-responsive,html.theme--catppuccin-mocha .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-mocha .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-mocha .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-mocha .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-mocha .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-mocha .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-mocha .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-mocha .content li+li{margin-top:0.25em}html.theme--catppuccin-mocha .content p:not(:last-child),html.theme--catppuccin-mocha .content dl:not(:last-child),html.theme--catppuccin-mocha .content ol:not(:last-child),html.theme--catppuccin-mocha .content ul:not(:last-child),html.theme--catppuccin-mocha .content blockquote:not(:last-child),html.theme--catppuccin-mocha .content pre:not(:last-child),html.theme--catppuccin-mocha .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-mocha .content h1,html.theme--catppuccin-mocha .content h2,html.theme--catppuccin-mocha .content h3,html.theme--catppuccin-mocha .content h4,html.theme--catppuccin-mocha .content h5,html.theme--catppuccin-mocha .content h6{color:#cdd6f4;font-weight:600;line-height:1.125}html.theme--catppuccin-mocha .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-mocha .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-mocha .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-mocha .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-mocha .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-mocha .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-mocha .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-mocha .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-mocha .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-mocha .content blockquote{background-color:#181825;border-left:5px solid #585b70;padding:1.25em 1.5em}html.theme--catppuccin-mocha .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-mocha .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-mocha .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-mocha .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-mocha .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-mocha .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-mocha .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-mocha .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-mocha .content ul ul ul{list-style-type:square}html.theme--catppuccin-mocha .content dd{margin-left:2em}html.theme--catppuccin-mocha .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-mocha .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-mocha .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-mocha .content figure img{display:inline-block}html.theme--catppuccin-mocha .content figure figcaption{font-style:italic}html.theme--catppuccin-mocha .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-mocha .content sup,html.theme--catppuccin-mocha .content sub{font-size:75%}html.theme--catppuccin-mocha .content table{width:100%}html.theme--catppuccin-mocha .content table td,html.theme--catppuccin-mocha .content table th{border:1px solid #585b70;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-mocha .content table th{color:#b8c5ef}html.theme--catppuccin-mocha .content table th:not([align]){text-align:inherit}html.theme--catppuccin-mocha .content table thead td,html.theme--catppuccin-mocha .content table thead th{border-width:0 0 2px;color:#b8c5ef}html.theme--catppuccin-mocha .content table tfoot td,html.theme--catppuccin-mocha .content table tfoot th{border-width:2px 0 0;color:#b8c5ef}html.theme--catppuccin-mocha .content table tbody tr:last-child td,html.theme--catppuccin-mocha .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-mocha .content .tabs li+li{margin-top:0}html.theme--catppuccin-mocha .content.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-mocha .content.is-normal{font-size:1rem}html.theme--catppuccin-mocha .content.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .content.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-mocha .icon.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-mocha .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-mocha .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-mocha .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-mocha .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-mocha .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-mocha div.icon-text{display:flex}html.theme--catppuccin-mocha .image,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-mocha .image img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-mocha .image img.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-mocha .image.is-fullwidth,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-mocha .image.is-square img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-mocha .image.is-square .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-mocha .image.is-1by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-mocha .image.is-1by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-mocha .image.is-5by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-mocha .image.is-5by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-mocha .image.is-4by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-mocha .image.is-4by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-mocha .image.is-3by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-mocha .image.is-5by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-mocha .image.is-5by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-mocha .image.is-16by9 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-mocha .image.is-16by9 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-mocha .image.is-2by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-mocha .image.is-2by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-mocha .image.is-3by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-mocha .image.is-3by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-mocha .image.is-4by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-mocha .image.is-4by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-mocha .image.is-3by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-mocha .image.is-3by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-mocha .image.is-2by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-mocha .image.is-2by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-mocha .image.is-3by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-mocha .image.is-9by16 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-mocha .image.is-9by16 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-mocha .image.is-1by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-mocha .image.is-1by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-mocha .image.is-1by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-mocha .image.is-1by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-mocha .image.is-square,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-mocha .image.is-1by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-mocha .image.is-5by4,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-mocha .image.is-4by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-mocha .image.is-3by2,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-mocha .image.is-5by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-mocha .image.is-16by9,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-mocha .image.is-2by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-mocha .image.is-3by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-mocha .image.is-4by5,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-mocha .image.is-3by4,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-mocha .image.is-2by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-mocha .image.is-3by5,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-mocha .image.is-9by16,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-mocha .image.is-1by2,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-mocha .image.is-1by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-mocha .image.is-16x16,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-mocha .image.is-24x24,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-mocha .image.is-32x32,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-mocha .image.is-48x48,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-mocha .image.is-64x64,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-mocha .image.is-96x96,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-mocha .image.is-128x128,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-mocha .notification{background-color:#181825;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-mocha .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-mocha .notification strong{color:currentColor}html.theme--catppuccin-mocha .notification code,html.theme--catppuccin-mocha .notification pre{background:#fff}html.theme--catppuccin-mocha .notification pre code{background:transparent}html.theme--catppuccin-mocha .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-mocha .notification .title,html.theme--catppuccin-mocha .notification .subtitle,html.theme--catppuccin-mocha .notification .content{color:currentColor}html.theme--catppuccin-mocha .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-dark,html.theme--catppuccin-mocha .content kbd.notification{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .notification.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.notification.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .notification.is-primary.is-light,html.theme--catppuccin-mocha details.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .notification.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .notification.is-link.is-light{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .notification.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-info.is-light{background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .notification.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-success.is-light{background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .notification.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-warning.is-light{background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .notification.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .notification.is-danger.is-light{background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-mocha .progress::-webkit-progress-bar{background-color:#45475a}html.theme--catppuccin-mocha .progress::-webkit-progress-value{background-color:#7f849c}html.theme--catppuccin-mocha .progress::-moz-progress-bar{background-color:#7f849c}html.theme--catppuccin-mocha .progress::-ms-fill{background-color:#7f849c;border:none}html.theme--catppuccin-mocha .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-mocha .content kbd.progress::-webkit-progress-value{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-mocha .content kbd.progress::-moz-progress-bar{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark::-ms-fill,html.theme--catppuccin-mocha .content kbd.progress::-ms-fill{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark:indeterminate,html.theme--catppuccin-mocha .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #313244 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-mocha details.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-mocha details.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary::-ms-fill,html.theme--catppuccin-mocha details.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary:indeterminate,html.theme--catppuccin-mocha details.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #89b4fa 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-link::-webkit-progress-value{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link::-moz-progress-bar{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link::-ms-fill{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link:indeterminate{background-image:linear-gradient(to right, #89b4fa 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-info::-webkit-progress-value{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info::-moz-progress-bar{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info::-ms-fill{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info:indeterminate{background-image:linear-gradient(to right, #94e2d5 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-success::-webkit-progress-value{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success::-moz-progress-bar{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success::-ms-fill{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6e3a1 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-warning::-webkit-progress-value{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning::-moz-progress-bar{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning::-ms-fill{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #f9e2af 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-danger::-webkit-progress-value{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger::-moz-progress-bar{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger::-ms-fill{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #f38ba8 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#45475a;background-image:linear-gradient(to right, #cdd6f4 30%, #45475a 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-mocha .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-mocha .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-mocha .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-mocha .progress.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-mocha .progress.is-medium{height:1.25rem}html.theme--catppuccin-mocha .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-mocha .table{background-color:#45475a;color:#cdd6f4}html.theme--catppuccin-mocha .table td,html.theme--catppuccin-mocha .table th{border:1px solid #585b70;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-mocha .table td.is-white,html.theme--catppuccin-mocha .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .table td.is-black,html.theme--catppuccin-mocha .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .table td.is-light,html.theme--catppuccin-mocha .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-dark,html.theme--catppuccin-mocha .table th.is-dark{background-color:#313244;border-color:#313244;color:#fff}html.theme--catppuccin-mocha .table td.is-primary,html.theme--catppuccin-mocha .table th.is-primary{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-link,html.theme--catppuccin-mocha .table th.is-link{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-info,html.theme--catppuccin-mocha .table th.is-info{background-color:#94e2d5;border-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-success,html.theme--catppuccin-mocha .table th.is-success{background-color:#a6e3a1;border-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-warning,html.theme--catppuccin-mocha .table th.is-warning{background-color:#f9e2af;border-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-danger,html.theme--catppuccin-mocha .table th.is-danger{background-color:#f38ba8;border-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .table td.is-narrow,html.theme--catppuccin-mocha .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-mocha .table td.is-selected,html.theme--catppuccin-mocha .table th.is-selected{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-selected a,html.theme--catppuccin-mocha .table td.is-selected strong,html.theme--catppuccin-mocha .table th.is-selected a,html.theme--catppuccin-mocha .table th.is-selected strong{color:currentColor}html.theme--catppuccin-mocha .table td.is-vcentered,html.theme--catppuccin-mocha .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-mocha .table th{color:#b8c5ef}html.theme--catppuccin-mocha .table th:not([align]){text-align:left}html.theme--catppuccin-mocha .table tr.is-selected{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table tr.is-selected a,html.theme--catppuccin-mocha .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-mocha .table tr.is-selected td,html.theme--catppuccin-mocha .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-mocha .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table thead td,html.theme--catppuccin-mocha .table thead th{border-width:0 0 2px;color:#b8c5ef}html.theme--catppuccin-mocha .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table tfoot td,html.theme--catppuccin-mocha .table tfoot th{border-width:2px 0 0;color:#b8c5ef}html.theme--catppuccin-mocha .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table tbody tr:last-child td,html.theme--catppuccin-mocha .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-mocha .table.is-bordered td,html.theme--catppuccin-mocha .table.is-bordered th{border-width:1px}html.theme--catppuccin-mocha .table.is-bordered tr:last-child td,html.theme--catppuccin-mocha .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-mocha .table.is-fullwidth{width:100%}html.theme--catppuccin-mocha .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#313244}html.theme--catppuccin-mocha .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#313244}html.theme--catppuccin-mocha .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#35364a}html.theme--catppuccin-mocha .table.is-narrow td,html.theme--catppuccin-mocha .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-mocha .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#313244}html.theme--catppuccin-mocha .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-mocha .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .tags .tag,html.theme--catppuccin-mocha .tags .content kbd,html.theme--catppuccin-mocha .content .tags kbd,html.theme--catppuccin-mocha .tags details.docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-mocha .tags .tag:not(:last-child),html.theme--catppuccin-mocha .tags .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags kbd:not(:last-child),html.theme--catppuccin-mocha .tags details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-mocha .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-mocha .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-mocha .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .tags.are-medium details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-mocha .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .tags.are-large details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-mocha .tags.is-centered{justify-content:center}html.theme--catppuccin-mocha .tags.is-centered .tag,html.theme--catppuccin-mocha .tags.is-centered .content kbd,html.theme--catppuccin-mocha .content .tags.is-centered kbd,html.theme--catppuccin-mocha .tags.is-centered details.docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-mocha .tags.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .tags.is-right .tag:not(:first-child),html.theme--catppuccin-mocha .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-mocha .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-mocha .tags.is-right details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-mocha .tags.is-right .tag:not(:last-child),html.theme--catppuccin-mocha .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-mocha .tags.is-right details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-mocha .tags.has-addons .tag,html.theme--catppuccin-mocha .tags.has-addons .content kbd,html.theme--catppuccin-mocha .content .tags.has-addons kbd,html.theme--catppuccin-mocha .tags.has-addons details.docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-mocha .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-mocha .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-mocha .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-mocha .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-mocha .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-mocha .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-mocha .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-mocha .tag:not(body),html.theme--catppuccin-mocha .content kbd:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#181825;border-radius:.4em;color:#cdd6f4;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-mocha .tag:not(body) .delete,html.theme--catppuccin-mocha .content kbd:not(body) .delete,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-mocha .tag.is-white:not(body),html.theme--catppuccin-mocha .content kbd.is-white:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .tag.is-black:not(body),html.theme--catppuccin-mocha .content kbd.is-black:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .tag.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-dark:not(body),html.theme--catppuccin-mocha .content kbd:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-mocha .content details.docstring>section>kbd:not(body){background-color:#313244;color:#fff}html.theme--catppuccin-mocha .tag.is-primary:not(body),html.theme--catppuccin-mocha .content kbd.is-primary:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:not(body){background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .tag.is-primary.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .tag.is-link:not(body),html.theme--catppuccin-mocha .content kbd.is-link:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .tag.is-link.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-link.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .tag.is-info:not(body),html.theme--catppuccin-mocha .content kbd.is-info:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-info.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-info.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .tag.is-success:not(body),html.theme--catppuccin-mocha .content kbd.is-success:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-success.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-success.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .tag.is-warning:not(body),html.theme--catppuccin-mocha .content kbd.is-warning:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-warning.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .tag.is-danger:not(body),html.theme--catppuccin-mocha .content kbd.is-danger:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .tag.is-danger.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .tag.is-normal:not(body),html.theme--catppuccin-mocha .content kbd.is-normal:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-mocha .tag.is-medium:not(body),html.theme--catppuccin-mocha .content kbd.is-medium:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-mocha .tag.is-large:not(body),html.theme--catppuccin-mocha .content kbd.is-large:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-mocha .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-mocha .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-mocha .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-mocha .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-mocha .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-mocha .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-mocha .tag.is-delete:not(body),html.theme--catppuccin-mocha .content kbd.is-delete:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-mocha .tag.is-delete:not(body)::before,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::before,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-mocha .tag.is-delete:not(body)::after,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::after,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-mocha .tag.is-delete:not(body)::before,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::before,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-mocha .tag.is-delete:not(body)::after,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::after,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-mocha .tag.is-delete:not(body):hover,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):hover,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-mocha .tag.is-delete:not(body):focus,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):focus,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#0e0e16}html.theme--catppuccin-mocha .tag.is-delete:not(body):active,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):active,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#040406}html.theme--catppuccin-mocha .tag.is-rounded:not(body),html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-mocha .content kbd.is-rounded:not(body),html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-mocha a.tag:hover,html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-mocha .title,html.theme--catppuccin-mocha .subtitle{word-break:break-word}html.theme--catppuccin-mocha .title em,html.theme--catppuccin-mocha .title span,html.theme--catppuccin-mocha .subtitle em,html.theme--catppuccin-mocha .subtitle span{font-weight:inherit}html.theme--catppuccin-mocha .title sub,html.theme--catppuccin-mocha .subtitle sub{font-size:.75em}html.theme--catppuccin-mocha .title sup,html.theme--catppuccin-mocha .subtitle sup{font-size:.75em}html.theme--catppuccin-mocha .title .tag,html.theme--catppuccin-mocha .title .content kbd,html.theme--catppuccin-mocha .content .title kbd,html.theme--catppuccin-mocha .title details.docstring>section>a.docs-sourcelink,html.theme--catppuccin-mocha .subtitle .tag,html.theme--catppuccin-mocha .subtitle .content kbd,html.theme--catppuccin-mocha .content .subtitle kbd,html.theme--catppuccin-mocha .subtitle details.docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-mocha .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-mocha .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-mocha .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-mocha .title.is-1{font-size:3rem}html.theme--catppuccin-mocha .title.is-2{font-size:2.5rem}html.theme--catppuccin-mocha .title.is-3{font-size:2rem}html.theme--catppuccin-mocha .title.is-4{font-size:1.5rem}html.theme--catppuccin-mocha .title.is-5{font-size:1.25rem}html.theme--catppuccin-mocha .title.is-6{font-size:1rem}html.theme--catppuccin-mocha .title.is-7{font-size:.75rem}html.theme--catppuccin-mocha .subtitle{color:#6c7086;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-mocha .subtitle strong{color:#6c7086;font-weight:600}html.theme--catppuccin-mocha .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-mocha .subtitle.is-1{font-size:3rem}html.theme--catppuccin-mocha .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-mocha .subtitle.is-3{font-size:2rem}html.theme--catppuccin-mocha .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-mocha .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-mocha .subtitle.is-6{font-size:1rem}html.theme--catppuccin-mocha .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-mocha .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-mocha .number{align-items:center;background-color:#181825;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{background-color:#1e1e2e;border-color:#585b70;border-radius:.4em;color:#7f849c}html.theme--catppuccin-mocha .select select::-moz-placeholder,html.theme--catppuccin-mocha .textarea::-moz-placeholder,html.theme--catppuccin-mocha .input::-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select::-webkit-input-placeholder,html.theme--catppuccin-mocha .textarea::-webkit-input-placeholder,html.theme--catppuccin-mocha .input::-webkit-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:-moz-placeholder,html.theme--catppuccin-mocha .textarea:-moz-placeholder,html.theme--catppuccin-mocha .input:-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:-ms-input-placeholder,html.theme--catppuccin-mocha .textarea:-ms-input-placeholder,html.theme--catppuccin-mocha .input:-ms-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:hover,html.theme--catppuccin-mocha .textarea:hover,html.theme--catppuccin-mocha .input:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-mocha .select select.is-hovered,html.theme--catppuccin-mocha .is-hovered.textarea,html.theme--catppuccin-mocha .is-hovered.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#6c7086}html.theme--catppuccin-mocha .select select:focus,html.theme--catppuccin-mocha .textarea:focus,html.theme--catppuccin-mocha .input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-mocha .select select.is-focused,html.theme--catppuccin-mocha .is-focused.textarea,html.theme--catppuccin-mocha .is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .select select:active,html.theme--catppuccin-mocha .textarea:active,html.theme--catppuccin-mocha .input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-mocha .select select.is-active,html.theme--catppuccin-mocha .is-active.textarea,html.theme--catppuccin-mocha .is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#89b4fa;box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select select[disabled],html.theme--catppuccin-mocha .textarea[disabled],html.theme--catppuccin-mocha .input[disabled],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-mocha .select select,fieldset[disabled] html.theme--catppuccin-mocha .textarea,fieldset[disabled] html.theme--catppuccin-mocha .input,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{background-color:#6c7086;border-color:#181825;box-shadow:none;color:#f7f8fd}html.theme--catppuccin-mocha .select select[disabled]::-moz-placeholder,html.theme--catppuccin-mocha .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-mocha .input[disabled]::-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]:-moz-placeholder,html.theme--catppuccin-mocha .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-mocha .input[disabled]:-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-mocha .textarea[readonly],html.theme--catppuccin-mocha .input[readonly],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-mocha .is-white.textarea,html.theme--catppuccin-mocha .is-white.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-mocha .is-white.textarea:focus,html.theme--catppuccin-mocha .is-white.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-mocha .is-white.is-focused.textarea,html.theme--catppuccin-mocha .is-white.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-white.textarea:active,html.theme--catppuccin-mocha .is-white.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-mocha .is-white.is-active.textarea,html.theme--catppuccin-mocha .is-white.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .is-black.textarea,html.theme--catppuccin-mocha .is-black.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-mocha .is-black.textarea:focus,html.theme--catppuccin-mocha .is-black.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-mocha .is-black.is-focused.textarea,html.theme--catppuccin-mocha .is-black.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-black.textarea:active,html.theme--catppuccin-mocha .is-black.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-mocha .is-black.is-active.textarea,html.theme--catppuccin-mocha .is-black.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .is-light.textarea,html.theme--catppuccin-mocha .is-light.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-mocha .is-light.textarea:focus,html.theme--catppuccin-mocha .is-light.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-mocha .is-light.is-focused.textarea,html.theme--catppuccin-mocha .is-light.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-light.textarea:active,html.theme--catppuccin-mocha .is-light.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-mocha .is-light.is-active.textarea,html.theme--catppuccin-mocha .is-light.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .is-dark.textarea,html.theme--catppuccin-mocha .content kbd.textarea,html.theme--catppuccin-mocha .is-dark.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-mocha .content kbd.input{border-color:#313244}html.theme--catppuccin-mocha .is-dark.textarea:focus,html.theme--catppuccin-mocha .content kbd.textarea:focus,html.theme--catppuccin-mocha .is-dark.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-mocha .content kbd.input:focus,html.theme--catppuccin-mocha .is-dark.is-focused.textarea,html.theme--catppuccin-mocha .content kbd.is-focused.textarea,html.theme--catppuccin-mocha .is-dark.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .content kbd.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-dark.textarea:active,html.theme--catppuccin-mocha .content kbd.textarea:active,html.theme--catppuccin-mocha .is-dark.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-mocha .content kbd.input:active,html.theme--catppuccin-mocha .is-dark.is-active.textarea,html.theme--catppuccin-mocha .content kbd.is-active.textarea,html.theme--catppuccin-mocha .is-dark.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .content kbd.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .is-primary.textarea,html.theme--catppuccin-mocha details.docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.input.docs-sourcelink{border-color:#89b4fa}html.theme--catppuccin-mocha .is-primary.textarea:focus,html.theme--catppuccin-mocha details.docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-mocha .is-primary.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-mocha details.docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-mocha .is-primary.is-focused.textarea,html.theme--catppuccin-mocha details.docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha details.docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.textarea:active,html.theme--catppuccin-mocha details.docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-mocha .is-primary.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-mocha details.docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-mocha .is-primary.is-active.textarea,html.theme--catppuccin-mocha details.docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha details.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .is-link.textarea,html.theme--catppuccin-mocha .is-link.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#89b4fa}html.theme--catppuccin-mocha .is-link.textarea:focus,html.theme--catppuccin-mocha .is-link.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-mocha .is-link.is-focused.textarea,html.theme--catppuccin-mocha .is-link.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-link.textarea:active,html.theme--catppuccin-mocha .is-link.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-mocha .is-link.is-active.textarea,html.theme--catppuccin-mocha .is-link.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .is-info.textarea,html.theme--catppuccin-mocha .is-info.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#94e2d5}html.theme--catppuccin-mocha .is-info.textarea:focus,html.theme--catppuccin-mocha .is-info.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-mocha .is-info.is-focused.textarea,html.theme--catppuccin-mocha .is-info.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-info.textarea:active,html.theme--catppuccin-mocha .is-info.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-mocha .is-info.is-active.textarea,html.theme--catppuccin-mocha .is-info.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .is-success.textarea,html.theme--catppuccin-mocha .is-success.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6e3a1}html.theme--catppuccin-mocha .is-success.textarea:focus,html.theme--catppuccin-mocha .is-success.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-mocha .is-success.is-focused.textarea,html.theme--catppuccin-mocha .is-success.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-success.textarea:active,html.theme--catppuccin-mocha .is-success.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-mocha .is-success.is-active.textarea,html.theme--catppuccin-mocha .is-success.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .is-warning.textarea,html.theme--catppuccin-mocha .is-warning.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#f9e2af}html.theme--catppuccin-mocha .is-warning.textarea:focus,html.theme--catppuccin-mocha .is-warning.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-mocha .is-warning.is-focused.textarea,html.theme--catppuccin-mocha .is-warning.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-warning.textarea:active,html.theme--catppuccin-mocha .is-warning.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-mocha .is-warning.is-active.textarea,html.theme--catppuccin-mocha .is-warning.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .is-danger.textarea,html.theme--catppuccin-mocha .is-danger.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#f38ba8}html.theme--catppuccin-mocha .is-danger.textarea:focus,html.theme--catppuccin-mocha .is-danger.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-mocha .is-danger.is-focused.textarea,html.theme--catppuccin-mocha .is-danger.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-danger.textarea:active,html.theme--catppuccin-mocha .is-danger.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-mocha .is-danger.is-active.textarea,html.theme--catppuccin-mocha .is-danger.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .is-small.textarea,html.theme--catppuccin-mocha .is-small.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-mocha .is-medium.textarea,html.theme--catppuccin-mocha .is-medium.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .is-large.textarea,html.theme--catppuccin-mocha .is-large.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .is-fullwidth.textarea,html.theme--catppuccin-mocha .is-fullwidth.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-mocha .is-inline.textarea,html.theme--catppuccin-mocha .is-inline.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-mocha .input.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-mocha .input.is-static,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-mocha .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-mocha .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-mocha .textarea[rows]{height:initial}html.theme--catppuccin-mocha .textarea.has-fixed-size{resize:none}html.theme--catppuccin-mocha .radio,html.theme--catppuccin-mocha .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-mocha .radio input,html.theme--catppuccin-mocha .checkbox input{cursor:pointer}html.theme--catppuccin-mocha .radio:hover,html.theme--catppuccin-mocha .checkbox:hover{color:#89dceb}html.theme--catppuccin-mocha .radio[disabled],html.theme--catppuccin-mocha .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-mocha .radio,fieldset[disabled] html.theme--catppuccin-mocha .checkbox,html.theme--catppuccin-mocha .radio input[disabled],html.theme--catppuccin-mocha .checkbox input[disabled]{color:#f7f8fd;cursor:not-allowed}html.theme--catppuccin-mocha .radio+.radio{margin-left:.5em}html.theme--catppuccin-mocha .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-mocha .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading)::after{border-color:#89b4fa;right:1.125em;z-index:4}html.theme--catppuccin-mocha .select.is-rounded select,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-mocha .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-mocha .select select::-ms-expand{display:none}html.theme--catppuccin-mocha .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-mocha .select select:hover{border-color:#181825}html.theme--catppuccin-mocha .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-mocha .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-mocha .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#89dceb}html.theme--catppuccin-mocha .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-mocha .select.is-white select{border-color:#fff}html.theme--catppuccin-mocha .select.is-white select:hover,html.theme--catppuccin-mocha .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-mocha .select.is-white select:focus,html.theme--catppuccin-mocha .select.is-white select.is-focused,html.theme--catppuccin-mocha .select.is-white select:active,html.theme--catppuccin-mocha .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-mocha .select.is-black select:hover,html.theme--catppuccin-mocha .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-mocha .select.is-black select:focus,html.theme--catppuccin-mocha .select.is-black select.is-focused,html.theme--catppuccin-mocha .select.is-black select:active,html.theme--catppuccin-mocha .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-mocha .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-mocha .select.is-light select:hover,html.theme--catppuccin-mocha .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-mocha .select.is-light select:focus,html.theme--catppuccin-mocha .select.is-light select.is-focused,html.theme--catppuccin-mocha .select.is-light select:active,html.theme--catppuccin-mocha .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .select.is-dark:not(:hover)::after,html.theme--catppuccin-mocha .content kbd.select:not(:hover)::after{border-color:#313244}html.theme--catppuccin-mocha .select.is-dark select,html.theme--catppuccin-mocha .content kbd.select select{border-color:#313244}html.theme--catppuccin-mocha .select.is-dark select:hover,html.theme--catppuccin-mocha .content kbd.select select:hover,html.theme--catppuccin-mocha .select.is-dark select.is-hovered,html.theme--catppuccin-mocha .content kbd.select select.is-hovered{border-color:#262735}html.theme--catppuccin-mocha .select.is-dark select:focus,html.theme--catppuccin-mocha .content kbd.select select:focus,html.theme--catppuccin-mocha .select.is-dark select.is-focused,html.theme--catppuccin-mocha .content kbd.select select.is-focused,html.theme--catppuccin-mocha .select.is-dark select:active,html.theme--catppuccin-mocha .content kbd.select select:active,html.theme--catppuccin-mocha .select.is-dark select.is-active,html.theme--catppuccin-mocha .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .select.is-primary:not(:hover)::after,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-primary select,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-primary select:hover,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-mocha .select.is-primary select.is-hovered,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#71a4f9}html.theme--catppuccin-mocha .select.is-primary select:focus,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-mocha .select.is-primary select.is-focused,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-mocha .select.is-primary select:active,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-mocha .select.is-primary select.is-active,html.theme--catppuccin-mocha details.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select.is-link:not(:hover)::after{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-link select{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-link select:hover,html.theme--catppuccin-mocha .select.is-link select.is-hovered{border-color:#71a4f9}html.theme--catppuccin-mocha .select.is-link select:focus,html.theme--catppuccin-mocha .select.is-link select.is-focused,html.theme--catppuccin-mocha .select.is-link select:active,html.theme--catppuccin-mocha .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select.is-info:not(:hover)::after{border-color:#94e2d5}html.theme--catppuccin-mocha .select.is-info select{border-color:#94e2d5}html.theme--catppuccin-mocha .select.is-info select:hover,html.theme--catppuccin-mocha .select.is-info select.is-hovered{border-color:#80ddcd}html.theme--catppuccin-mocha .select.is-info select:focus,html.theme--catppuccin-mocha .select.is-info select.is-focused,html.theme--catppuccin-mocha .select.is-info select:active,html.theme--catppuccin-mocha .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .select.is-success:not(:hover)::after{border-color:#a6e3a1}html.theme--catppuccin-mocha .select.is-success select{border-color:#a6e3a1}html.theme--catppuccin-mocha .select.is-success select:hover,html.theme--catppuccin-mocha .select.is-success select.is-hovered{border-color:#93dd8d}html.theme--catppuccin-mocha .select.is-success select:focus,html.theme--catppuccin-mocha .select.is-success select.is-focused,html.theme--catppuccin-mocha .select.is-success select:active,html.theme--catppuccin-mocha .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .select.is-warning:not(:hover)::after{border-color:#f9e2af}html.theme--catppuccin-mocha .select.is-warning select{border-color:#f9e2af}html.theme--catppuccin-mocha .select.is-warning select:hover,html.theme--catppuccin-mocha .select.is-warning select.is-hovered{border-color:#f7d997}html.theme--catppuccin-mocha .select.is-warning select:focus,html.theme--catppuccin-mocha .select.is-warning select.is-focused,html.theme--catppuccin-mocha .select.is-warning select:active,html.theme--catppuccin-mocha .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .select.is-danger:not(:hover)::after{border-color:#f38ba8}html.theme--catppuccin-mocha .select.is-danger select{border-color:#f38ba8}html.theme--catppuccin-mocha .select.is-danger select:hover,html.theme--catppuccin-mocha .select.is-danger select.is-hovered{border-color:#f17497}html.theme--catppuccin-mocha .select.is-danger select:focus,html.theme--catppuccin-mocha .select.is-danger select.is-focused,html.theme--catppuccin-mocha .select.is-danger select:active,html.theme--catppuccin-mocha .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .select.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-mocha .select.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .select.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .select.is-disabled::after{border-color:#f7f8fd !important;opacity:0.5}html.theme--catppuccin-mocha .select.is-fullwidth{width:100%}html.theme--catppuccin-mocha .select.is-fullwidth select{width:100%}html.theme--catppuccin-mocha .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-mocha .select.is-loading.is-small:after,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-mocha .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-mocha .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-mocha .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-mocha .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:hover .file-cta,html.theme--catppuccin-mocha .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:focus .file-cta,html.theme--catppuccin-mocha .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:active .file-cta,html.theme--catppuccin-mocha .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-black:hover .file-cta,html.theme--catppuccin-mocha .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-black:focus .file-cta,html.theme--catppuccin-mocha .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-black:active .file-cta,html.theme--catppuccin-mocha .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:hover .file-cta,html.theme--catppuccin-mocha .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:focus .file-cta,html.theme--catppuccin-mocha .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:active .file-cta,html.theme--catppuccin-mocha .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-dark .file-cta,html.theme--catppuccin-mocha .content kbd.file .file-cta{background-color:#313244;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-dark:hover .file-cta,html.theme--catppuccin-mocha .content kbd.file:hover .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-hovered .file-cta{background-color:#2c2d3d;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-dark:focus .file-cta,html.theme--catppuccin-mocha .content kbd.file:focus .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-focused .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(49,50,68,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-dark:active .file-cta,html.theme--catppuccin-mocha .content kbd.file:active .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-active .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-active .file-cta{background-color:#262735;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary:hover .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary:focus .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-focused .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(137,180,250,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-primary:active .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-active .file-cta,html.theme--catppuccin-mocha details.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link .file-cta{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link:hover .file-cta,html.theme--catppuccin-mocha .file.is-link.is-hovered .file-cta{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link:focus .file-cta,html.theme--catppuccin-mocha .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(137,180,250,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-link:active .file-cta,html.theme--catppuccin-mocha .file.is-link.is-active .file-cta{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-info .file-cta{background-color:#94e2d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:hover .file-cta,html.theme--catppuccin-mocha .file.is-info.is-hovered .file-cta{background-color:#8adfd1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:focus .file-cta,html.theme--catppuccin-mocha .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(148,226,213,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:active .file-cta,html.theme--catppuccin-mocha .file.is-info.is-active .file-cta{background-color:#80ddcd;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success .file-cta{background-color:#a6e3a1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:hover .file-cta,html.theme--catppuccin-mocha .file.is-success.is-hovered .file-cta{background-color:#9de097;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:focus .file-cta,html.theme--catppuccin-mocha .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,227,161,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:active .file-cta,html.theme--catppuccin-mocha .file.is-success.is-active .file-cta{background-color:#93dd8d;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning .file-cta{background-color:#f9e2af;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:hover .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-hovered .file-cta{background-color:#f8dea3;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:focus .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(249,226,175,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:active .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-active .file-cta{background-color:#f7d997;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-danger .file-cta{background-color:#f38ba8;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-danger:hover .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-hovered .file-cta{background-color:#f27f9f;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-danger:focus .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(243,139,168,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-danger:active .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-active .file-cta{background-color:#f17497;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-mocha .file.is-normal{font-size:1rem}html.theme--catppuccin-mocha .file.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-mocha .file.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-mocha .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-mocha .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-mocha .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-mocha .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-mocha .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-mocha .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-mocha .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-mocha .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-mocha .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-mocha .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-mocha .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-mocha .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-mocha .file.is-centered{justify-content:center}html.theme--catppuccin-mocha .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-mocha .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-mocha .file.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-mocha .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-mocha .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-mocha .file-label:hover .file-cta{background-color:#2c2d3d;color:#b8c5ef}html.theme--catppuccin-mocha .file-label:hover .file-name{border-color:#525569}html.theme--catppuccin-mocha .file-label:active .file-cta{background-color:#262735;color:#b8c5ef}html.theme--catppuccin-mocha .file-label:active .file-name{border-color:#4d4f62}html.theme--catppuccin-mocha .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha .file-name{border-color:#585b70;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-mocha .file-cta{background-color:#313244;color:#cdd6f4}html.theme--catppuccin-mocha .file-name{border-color:#585b70;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-mocha .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-mocha .file-icon .fa{font-size:14px}html.theme--catppuccin-mocha .label{color:#b8c5ef;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-mocha .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-mocha .label.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-mocha .label.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .label.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-mocha .help.is-white{color:#fff}html.theme--catppuccin-mocha .help.is-black{color:#0a0a0a}html.theme--catppuccin-mocha .help.is-light{color:#f5f5f5}html.theme--catppuccin-mocha .help.is-dark,html.theme--catppuccin-mocha .content kbd.help{color:#313244}html.theme--catppuccin-mocha .help.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.help.docs-sourcelink{color:#89b4fa}html.theme--catppuccin-mocha .help.is-link{color:#89b4fa}html.theme--catppuccin-mocha .help.is-info{color:#94e2d5}html.theme--catppuccin-mocha .help.is-success{color:#a6e3a1}html.theme--catppuccin-mocha .help.is-warning{color:#f9e2af}html.theme--catppuccin-mocha .help.is-danger{color:#f38ba8}html.theme--catppuccin-mocha .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-mocha .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-mocha .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-mocha .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-mocha .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-mocha .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-mocha .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-mocha .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-mocha .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field.is-horizontal{display:flex}}html.theme--catppuccin-mocha .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-mocha .field-label.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-mocha .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-mocha .field-body .field{margin-bottom:0}html.theme--catppuccin-mocha .field-body>.field{flex-shrink:1}html.theme--catppuccin-mocha .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-mocha .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-mocha .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-mocha .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select:focus~.icon{color:#313244}html.theme--catppuccin-mocha .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-mocha .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-mocha .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-mocha .control.has-icons-left .icon,html.theme--catppuccin-mocha .control.has-icons-right .icon{color:#585b70;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-mocha .control.has-icons-left .input,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-mocha .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-mocha .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-mocha .control.has-icons-right .input,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-mocha .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-mocha .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-mocha .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-mocha .control.is-loading.is-small:after,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-mocha .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-mocha .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-mocha .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-mocha .breadcrumb a{align-items:center;color:#89b4fa;display:initial;justify-content:center;padding:0 .75em}html.theme--catppuccin-mocha .breadcrumb a:hover{color:#89dceb}html.theme--catppuccin-mocha .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-mocha .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-mocha .breadcrumb li.is-active a{color:#b8c5ef;cursor:default;pointer-events:none}html.theme--catppuccin-mocha .breadcrumb li+li::before{color:#6c7086;content:"\0002f"}html.theme--catppuccin-mocha .breadcrumb ul,html.theme--catppuccin-mocha .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-mocha .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-mocha .breadcrumb.is-centered ol,html.theme--catppuccin-mocha .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-mocha .breadcrumb.is-right ol,html.theme--catppuccin-mocha .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-mocha .breadcrumb.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-mocha .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-mocha .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-mocha .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-mocha .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-mocha .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#cdd6f4;max-width:100%;position:relative}html.theme--catppuccin-mocha .card-footer:first-child,html.theme--catppuccin-mocha .card-content:first-child,html.theme--catppuccin-mocha .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-mocha .card-footer:last-child,html.theme--catppuccin-mocha .card-content:last-child,html.theme--catppuccin-mocha .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-mocha .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-mocha .card-header-title{align-items:center;color:#b8c5ef;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-mocha .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-mocha .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-mocha .card-image{display:block;position:relative}html.theme--catppuccin-mocha .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-mocha .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-mocha .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-mocha .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-mocha .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-mocha .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-mocha .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-mocha .dropdown.is-active .dropdown-menu,html.theme--catppuccin-mocha .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-mocha .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-mocha .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-mocha .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-mocha .dropdown-content{background-color:#181825;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-mocha .dropdown-item{color:#cdd6f4;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-mocha a.dropdown-item,html.theme--catppuccin-mocha button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-mocha a.dropdown-item:hover,html.theme--catppuccin-mocha button.dropdown-item:hover{background-color:#181825;color:#0a0a0a}html.theme--catppuccin-mocha a.dropdown-item.is-active,html.theme--catppuccin-mocha button.dropdown-item.is-active{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-mocha .level{align-items:center;justify-content:space-between}html.theme--catppuccin-mocha .level code{border-radius:.4em}html.theme--catppuccin-mocha .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-mocha .level.is-mobile{display:flex}html.theme--catppuccin-mocha .level.is-mobile .level-left,html.theme--catppuccin-mocha .level.is-mobile .level-right{display:flex}html.theme--catppuccin-mocha .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-mocha .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-mocha .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level{display:flex}html.theme--catppuccin-mocha .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-mocha .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-mocha .level-item .title,html.theme--catppuccin-mocha .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-mocha .level-left,html.theme--catppuccin-mocha .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .level-left .level-item.is-flexible,html.theme--catppuccin-mocha .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-left .level-item:not(:last-child),html.theme--catppuccin-mocha .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-mocha .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-left{display:flex}}html.theme--catppuccin-mocha .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-right{display:flex}}html.theme--catppuccin-mocha .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-mocha .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-mocha .media .media{border-top:1px solid rgba(88,91,112,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-mocha .media .media .content:not(:last-child),html.theme--catppuccin-mocha .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-mocha .media .media .media{padding-top:.5rem}html.theme--catppuccin-mocha .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-mocha .media+.media{border-top:1px solid rgba(88,91,112,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-mocha .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-mocha .media-left,html.theme--catppuccin-mocha .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .media-left{margin-right:1rem}html.theme--catppuccin-mocha .media-right{margin-left:1rem}html.theme--catppuccin-mocha .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .media-content{overflow-x:auto}}html.theme--catppuccin-mocha .menu{font-size:1rem}html.theme--catppuccin-mocha .menu.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-mocha .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .menu.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .menu-list{line-height:1.25}html.theme--catppuccin-mocha .menu-list a{border-radius:3px;color:#cdd6f4;display:block;padding:0.5em 0.75em}html.theme--catppuccin-mocha .menu-list a:hover{background-color:#181825;color:#b8c5ef}html.theme--catppuccin-mocha .menu-list a.is-active{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .menu-list li ul{border-left:1px solid #585b70;margin:.75em;padding-left:.75em}html.theme--catppuccin-mocha .menu-label{color:#f7f8fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-mocha .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-mocha .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-mocha .message{background-color:#181825;border-radius:.4em;font-size:1rem}html.theme--catppuccin-mocha .message strong{color:currentColor}html.theme--catppuccin-mocha .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-mocha .message.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-mocha .message.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .message.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .message.is-white{background-color:#fff}html.theme--catppuccin-mocha .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-mocha .message.is-black{background-color:#fafafa}html.theme--catppuccin-mocha .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-mocha .message.is-light{background-color:#fafafa}html.theme--catppuccin-mocha .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-mocha .message.is-dark,html.theme--catppuccin-mocha .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-mocha .message.is-dark .message-header,html.theme--catppuccin-mocha .content kbd.message .message-header{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .message.is-dark .message-body,html.theme--catppuccin-mocha .content kbd.message .message-body{border-color:#313244}html.theme--catppuccin-mocha .message.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.message.docs-sourcelink{background-color:#ebf3fe}html.theme--catppuccin-mocha .message.is-primary .message-header,html.theme--catppuccin-mocha details.docstring>section>a.message.docs-sourcelink .message-header{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .message.is-primary .message-body,html.theme--catppuccin-mocha details.docstring>section>a.message.docs-sourcelink .message-body{border-color:#89b4fa;color:#063c93}html.theme--catppuccin-mocha .message.is-link{background-color:#ebf3fe}html.theme--catppuccin-mocha .message.is-link .message-header{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .message.is-link .message-body{border-color:#89b4fa;color:#063c93}html.theme--catppuccin-mocha .message.is-info{background-color:#effbf9}html.theme--catppuccin-mocha .message.is-info .message-header{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-info .message-body{border-color:#94e2d5;color:#207466}html.theme--catppuccin-mocha .message.is-success{background-color:#f0faef}html.theme--catppuccin-mocha .message.is-success .message-header{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-success .message-body{border-color:#a6e3a1;color:#287222}html.theme--catppuccin-mocha .message.is-warning{background-color:#fef8ec}html.theme--catppuccin-mocha .message.is-warning .message-header{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-warning .message-body{border-color:#f9e2af;color:#8a620a}html.theme--catppuccin-mocha .message.is-danger{background-color:#fdedf1}html.theme--catppuccin-mocha .message.is-danger .message-header{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .message.is-danger .message-body{border-color:#f38ba8;color:#991036}html.theme--catppuccin-mocha .message-header{align-items:center;background-color:#cdd6f4;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-mocha .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-mocha .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .message-body{border-color:#585b70;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#cdd6f4;padding:1.25em 1.5em}html.theme--catppuccin-mocha .message-body code,html.theme--catppuccin-mocha .message-body pre{background-color:#fff}html.theme--catppuccin-mocha .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-mocha .modal.is-active{display:flex}html.theme--catppuccin-mocha .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-mocha .modal-content,html.theme--catppuccin-mocha .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-mocha .modal-content,html.theme--catppuccin-mocha .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-mocha .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-mocha .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-mocha .modal-card-head,html.theme--catppuccin-mocha .modal-card-foot{align-items:center;background-color:#181825;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-mocha .modal-card-head{border-bottom:1px solid #585b70;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-mocha .modal-card-title{color:#cdd6f4;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-mocha .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #585b70}html.theme--catppuccin-mocha .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-mocha .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#1e1e2e;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-mocha .navbar{background-color:#89b4fa;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-mocha .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-mocha .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-mocha .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-dark,html.theme--catppuccin-mocha .content kbd.navbar{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-burger,html.theme--catppuccin-mocha .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#313244;color:#fff}}html.theme--catppuccin-mocha .navbar.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-burger,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa;color:#fff}}html.theme--catppuccin-mocha .navbar.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa;color:#fff}}html.theme--catppuccin-mocha .navbar.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#94e2d5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#f9e2af;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f38ba8;color:#fff}}html.theme--catppuccin-mocha .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-mocha .navbar.has-shadow{box-shadow:0 2px 0 0 #181825}html.theme--catppuccin-mocha .navbar.is-fixed-bottom,html.theme--catppuccin-mocha .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #181825}html.theme--catppuccin-mocha .navbar.is-fixed-top{top:0}html.theme--catppuccin-mocha html.has-navbar-fixed-top,html.theme--catppuccin-mocha body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-mocha .navbar-brand,html.theme--catppuccin-mocha .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-mocha .navbar-brand a.navbar-item:focus,html.theme--catppuccin-mocha .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-mocha .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-mocha .navbar-burger{color:#cdd6f4;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-mocha .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-mocha .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-mocha .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-mocha .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-mocha .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-mocha .navbar-menu{display:none}html.theme--catppuccin-mocha .navbar-item,html.theme--catppuccin-mocha .navbar-link{color:#cdd6f4;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-mocha .navbar-item .icon:only-child,html.theme--catppuccin-mocha .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-mocha a.navbar-item,html.theme--catppuccin-mocha .navbar-link{cursor:pointer}html.theme--catppuccin-mocha a.navbar-item:focus,html.theme--catppuccin-mocha a.navbar-item:focus-within,html.theme--catppuccin-mocha a.navbar-item:hover,html.theme--catppuccin-mocha a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar-link:focus,html.theme--catppuccin-mocha .navbar-link:focus-within,html.theme--catppuccin-mocha .navbar-link:hover,html.theme--catppuccin-mocha .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}html.theme--catppuccin-mocha .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .navbar-item img{max-height:1.75rem}html.theme--catppuccin-mocha .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-mocha .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-mocha .navbar-item.is-tab:focus,html.theme--catppuccin-mocha .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#89b4fa;border-bottom-style:solid;border-bottom-width:3px;color:#89b4fa;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-mocha .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-mocha .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-mocha .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-mocha .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .navbar>.container{display:block}html.theme--catppuccin-mocha .navbar-brand .navbar-item,html.theme--catppuccin-mocha .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-mocha .navbar-link::after{display:none}html.theme--catppuccin-mocha .navbar-menu{background-color:#89b4fa;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-mocha .navbar-menu.is-active{display:block}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch,html.theme--catppuccin-mocha .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-mocha .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-mocha .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-mocha html.has-navbar-fixed-top-touch,html.theme--catppuccin-mocha body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar,html.theme--catppuccin-mocha .navbar-menu,html.theme--catppuccin-mocha .navbar-start,html.theme--catppuccin-mocha .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-mocha .navbar{min-height:4rem}html.theme--catppuccin-mocha .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-mocha .navbar.is-spaced .navbar-start,html.theme--catppuccin-mocha .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-mocha .navbar.is-spaced a.navbar-item,html.theme--catppuccin-mocha .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#7f849c}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}html.theme--catppuccin-mocha .navbar-burger{display:none}html.theme--catppuccin-mocha .navbar-item,html.theme--catppuccin-mocha .navbar-link{align-items:center;display:flex}html.theme--catppuccin-mocha .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-mocha .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-mocha .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-mocha .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-mocha .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-mocha .navbar-dropdown{background-color:#89b4fa;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-mocha .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#7f849c}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}.navbar.is-spaced html.theme--catppuccin-mocha .navbar-dropdown,html.theme--catppuccin-mocha .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-mocha .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-mocha .navbar-divider{display:block}html.theme--catppuccin-mocha .navbar>.container .navbar-brand,html.theme--catppuccin-mocha .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-mocha .navbar>.container .navbar-menu,html.theme--catppuccin-mocha .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-mocha .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-mocha html.has-navbar-fixed-top-desktop,html.theme--catppuccin-mocha body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-mocha html.has-spaced-navbar-fixed-top,html.theme--catppuccin-mocha body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-mocha html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-mocha body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-mocha a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar-link.is-active{color:#89b4fa}html.theme--catppuccin-mocha a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-mocha .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-mocha .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-mocha .pagination.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-mocha .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .pagination.is-rounded .pagination-previous,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-mocha .pagination.is-rounded .pagination-next,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-mocha .pagination.is-rounded .pagination-link,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-mocha .pagination,html.theme--catppuccin-mocha .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link{border-color:#585b70;color:#89b4fa;min-width:2.5em}html.theme--catppuccin-mocha .pagination-previous:hover,html.theme--catppuccin-mocha .pagination-next:hover,html.theme--catppuccin-mocha .pagination-link:hover{border-color:#6c7086;color:#89dceb}html.theme--catppuccin-mocha .pagination-previous:focus,html.theme--catppuccin-mocha .pagination-next:focus,html.theme--catppuccin-mocha .pagination-link:focus{border-color:#6c7086}html.theme--catppuccin-mocha .pagination-previous:active,html.theme--catppuccin-mocha .pagination-next:active,html.theme--catppuccin-mocha .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-mocha .pagination-previous[disabled],html.theme--catppuccin-mocha .pagination-previous.is-disabled,html.theme--catppuccin-mocha .pagination-next[disabled],html.theme--catppuccin-mocha .pagination-next.is-disabled,html.theme--catppuccin-mocha .pagination-link[disabled],html.theme--catppuccin-mocha .pagination-link.is-disabled{background-color:#585b70;border-color:#585b70;box-shadow:none;color:#f7f8fd;opacity:0.5}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-mocha .pagination-link.is-current{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .pagination-ellipsis{color:#6c7086;pointer-events:none}html.theme--catppuccin-mocha .pagination-list{flex-wrap:wrap}html.theme--catppuccin-mocha .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .pagination{flex-wrap:wrap}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-mocha .pagination-previous{order:2}html.theme--catppuccin-mocha .pagination-next{order:3}html.theme--catppuccin-mocha .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-mocha .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-mocha .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-mocha .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-mocha .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-mocha .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-mocha .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-mocha .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-mocha .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-mocha .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-mocha .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-mocha .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-mocha .panel.is-dark .panel-heading,html.theme--catppuccin-mocha .content kbd.panel .panel-heading{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-mocha .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#313244}html.theme--catppuccin-mocha .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-mocha .content kbd.panel .panel-block.is-active .panel-icon{color:#313244}html.theme--catppuccin-mocha .panel.is-primary .panel-heading,html.theme--catppuccin-mocha details.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-mocha details.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-mocha details.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel.is-link .panel-heading{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .panel.is-link .panel-tabs a.is-active{border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .panel.is-link .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel.is-info .panel-heading{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-info .panel-tabs a.is-active{border-bottom-color:#94e2d5}html.theme--catppuccin-mocha .panel.is-info .panel-block.is-active .panel-icon{color:#94e2d5}html.theme--catppuccin-mocha .panel.is-success .panel-heading{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6e3a1}html.theme--catppuccin-mocha .panel.is-success .panel-block.is-active .panel-icon{color:#a6e3a1}html.theme--catppuccin-mocha .panel.is-warning .panel-heading{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#f9e2af}html.theme--catppuccin-mocha .panel.is-warning .panel-block.is-active .panel-icon{color:#f9e2af}html.theme--catppuccin-mocha .panel.is-danger .panel-heading{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f38ba8}html.theme--catppuccin-mocha .panel.is-danger .panel-block.is-active .panel-icon{color:#f38ba8}html.theme--catppuccin-mocha .panel-tabs:not(:last-child),html.theme--catppuccin-mocha .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-mocha .panel-heading{background-color:#45475a;border-radius:8px 8px 0 0;color:#b8c5ef;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-mocha .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-mocha .panel-tabs a{border-bottom:1px solid #585b70;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-mocha .panel-tabs a.is-active{border-bottom-color:#45475a;color:#71a4f9}html.theme--catppuccin-mocha .panel-list a{color:#cdd6f4}html.theme--catppuccin-mocha .panel-list a:hover{color:#89b4fa}html.theme--catppuccin-mocha .panel-block{align-items:center;color:#b8c5ef;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-mocha .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-mocha .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-mocha .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-mocha .panel-block.is-active{border-left-color:#89b4fa;color:#71a4f9}html.theme--catppuccin-mocha .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-mocha a.panel-block,html.theme--catppuccin-mocha label.panel-block{cursor:pointer}html.theme--catppuccin-mocha a.panel-block:hover,html.theme--catppuccin-mocha label.panel-block:hover{background-color:#181825}html.theme--catppuccin-mocha .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f7f8fd;margin-right:.75em}html.theme--catppuccin-mocha .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-mocha .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-mocha .tabs a{align-items:center;border-bottom-color:#585b70;border-bottom-style:solid;border-bottom-width:1px;color:#cdd6f4;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-mocha .tabs a:hover{border-bottom-color:#b8c5ef;color:#b8c5ef}html.theme--catppuccin-mocha .tabs li{display:block}html.theme--catppuccin-mocha .tabs li.is-active a{border-bottom-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .tabs ul{align-items:center;border-bottom-color:#585b70;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-mocha .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-mocha .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-mocha .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-mocha .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-mocha .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-mocha .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-mocha .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-mocha .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-mocha .tabs.is-boxed a:hover{background-color:#181825;border-bottom-color:#585b70}html.theme--catppuccin-mocha .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#585b70;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-mocha .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .tabs.is-toggle a{border-color:#585b70;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-mocha .tabs.is-toggle a:hover{background-color:#181825;border-color:#6c7086;z-index:2}html.theme--catppuccin-mocha .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-mocha .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-mocha .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-mocha .tabs.is-toggle li.is-active a{background-color:#89b4fa;border-color:#89b4fa;color:#fff;z-index:1}html.theme--catppuccin-mocha .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-mocha .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-mocha .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-mocha .tabs.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-mocha .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .column.is-narrow,html.theme--catppuccin-mocha .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full,html.theme--catppuccin-mocha .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters,html.theme--catppuccin-mocha .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds,html.theme--catppuccin-mocha .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half,html.theme--catppuccin-mocha .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third,html.theme--catppuccin-mocha .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter,html.theme--catppuccin-mocha .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth,html.theme--catppuccin-mocha .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths,html.theme--catppuccin-mocha .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths,html.theme--catppuccin-mocha .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths,html.theme--catppuccin-mocha .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters,html.theme--catppuccin-mocha .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds,html.theme--catppuccin-mocha .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half,html.theme--catppuccin-mocha .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third,html.theme--catppuccin-mocha .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter,html.theme--catppuccin-mocha .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth,html.theme--catppuccin-mocha .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths,html.theme--catppuccin-mocha .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths,html.theme--catppuccin-mocha .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths,html.theme--catppuccin-mocha .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-mocha .column.is-0,html.theme--catppuccin-mocha .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0,html.theme--catppuccin-mocha .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-mocha .column.is-1,html.theme--catppuccin-mocha .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1,html.theme--catppuccin-mocha .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2,html.theme--catppuccin-mocha .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2,html.theme--catppuccin-mocha .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3,html.theme--catppuccin-mocha .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3,html.theme--catppuccin-mocha .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-mocha .column.is-4,html.theme--catppuccin-mocha .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4,html.theme--catppuccin-mocha .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5,html.theme--catppuccin-mocha .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5,html.theme--catppuccin-mocha .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6,html.theme--catppuccin-mocha .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6,html.theme--catppuccin-mocha .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-mocha .column.is-7,html.theme--catppuccin-mocha .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7,html.theme--catppuccin-mocha .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8,html.theme--catppuccin-mocha .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8,html.theme--catppuccin-mocha .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9,html.theme--catppuccin-mocha .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9,html.theme--catppuccin-mocha .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-mocha .column.is-10,html.theme--catppuccin-mocha .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10,html.theme--catppuccin-mocha .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11,html.theme--catppuccin-mocha .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11,html.theme--catppuccin-mocha .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12,html.theme--catppuccin-mocha .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12,html.theme--catppuccin-mocha .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-mocha .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-mocha .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-mocha .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-mocha .columns.is-centered{justify-content:center}html.theme--catppuccin-mocha .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-mocha .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-mocha .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-mocha .columns.is-mobile{display:flex}html.theme--catppuccin-mocha .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-mocha .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-desktop{display:flex}}html.theme--catppuccin-mocha .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-mocha .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-mocha .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-mocha .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-mocha .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-mocha .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-mocha .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-mocha .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-mocha .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-mocha .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-mocha .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-mocha .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-mocha .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-mocha .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-mocha .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-mocha .tile.is-child{margin:0 !important}html.theme--catppuccin-mocha .tile.is-parent{padding:.75rem}html.theme--catppuccin-mocha .tile.is-vertical{flex-direction:column}html.theme--catppuccin-mocha .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .tile:not(.is-child){display:flex}html.theme--catppuccin-mocha .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .tile.is-3{flex:none;width:25%}html.theme--catppuccin-mocha .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .tile.is-6{flex:none;width:50%}html.theme--catppuccin-mocha .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .tile.is-9{flex:none;width:75%}html.theme--catppuccin-mocha .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-mocha .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-mocha .hero .navbar{background:none}html.theme--catppuccin-mocha .hero .tabs ul{border-bottom:none}html.theme--catppuccin-mocha .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-white strong{color:inherit}html.theme--catppuccin-mocha .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-mocha .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-mocha .hero.is-white .navbar-item,html.theme--catppuccin-mocha .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-mocha .hero.is-white a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-white .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-mocha .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-mocha .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-black strong{color:inherit}html.theme--catppuccin-mocha .hero.is-black .title{color:#fff}html.theme--catppuccin-mocha .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-mocha .hero.is-black .navbar-item,html.theme--catppuccin-mocha .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-black a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-black .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-mocha .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-light strong{color:inherit}html.theme--catppuccin-mocha .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-mocha .hero.is-light .navbar-item,html.theme--catppuccin-mocha .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-light .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-mocha .hero.is-dark,html.theme--catppuccin-mocha .content kbd.hero{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-dark strong,html.theme--catppuccin-mocha .content kbd.hero strong{color:inherit}html.theme--catppuccin-mocha .hero.is-dark .title,html.theme--catppuccin-mocha .content kbd.hero .title{color:#fff}html.theme--catppuccin-mocha .hero.is-dark .subtitle,html.theme--catppuccin-mocha .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-mocha .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-dark .subtitle strong,html.theme--catppuccin-mocha .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-dark .navbar-menu,html.theme--catppuccin-mocha .content kbd.hero .navbar-menu{background-color:#313244}}html.theme--catppuccin-mocha .hero.is-dark .navbar-item,html.theme--catppuccin-mocha .content kbd.hero .navbar-item,html.theme--catppuccin-mocha .hero.is-dark .navbar-link,html.theme--catppuccin-mocha .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-dark .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.hero .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.hero .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .hero.is-dark .tabs a,html.theme--catppuccin-mocha .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-dark .tabs a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs li.is-active a{color:#313244 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#313244}html.theme--catppuccin-mocha .hero.is-dark.is-bold,html.theme--catppuccin-mocha .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #181c2a 0%, #313244 71%, #3c3856 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-mocha .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #181c2a 0%, #313244 71%, #3c3856 100%)}}html.theme--catppuccin-mocha .hero.is-primary,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-primary strong,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-mocha .hero.is-primary .title,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-mocha .hero.is-primary .subtitle,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-primary .subtitle strong,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-primary .navbar-menu,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#89b4fa}}html.theme--catppuccin-mocha .hero.is-primary .navbar-item,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-mocha .hero.is-primary .navbar-link,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-primary .navbar-link:hover,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .hero.is-primary .tabs a,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-primary .tabs a:hover,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#89b4fa !important;opacity:1}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .hero.is-primary.is-bold,html.theme--catppuccin-mocha details.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-mocha details.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}}html.theme--catppuccin-mocha .hero.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-link strong{color:inherit}html.theme--catppuccin-mocha .hero.is-link .title{color:#fff}html.theme--catppuccin-mocha .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-link .navbar-menu{background-color:#89b4fa}}html.theme--catppuccin-mocha .hero.is-link .navbar-item,html.theme--catppuccin-mocha .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-link a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-link .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-link .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-link .tabs li.is-active a{color:#89b4fa !important;opacity:1}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .hero.is-link.is-bold{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}}html.theme--catppuccin-mocha .hero.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-info strong{color:inherit}html.theme--catppuccin-mocha .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-info .navbar-menu{background-color:#94e2d5}}html.theme--catppuccin-mocha .hero.is-info .navbar-item,html.theme--catppuccin-mocha .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-info .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-info .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-info .tabs li.is-active a{color:#94e2d5 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .hero.is-info.is-bold{background-image:linear-gradient(141deg, #63e0b6 0%, #94e2d5 71%, #a5eaea 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #63e0b6 0%, #94e2d5 71%, #a5eaea 100%)}}html.theme--catppuccin-mocha .hero.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-success strong{color:inherit}html.theme--catppuccin-mocha .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-success .navbar-menu{background-color:#a6e3a1}}html.theme--catppuccin-mocha .hero.is-success .navbar-item,html.theme--catppuccin-mocha .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-success .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-success .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-success .tabs li.is-active a{color:#a6e3a1 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .hero.is-success.is-bold{background-image:linear-gradient(141deg, #8ce071 0%, #a6e3a1 71%, #b2ebb7 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #8ce071 0%, #a6e3a1 71%, #b2ebb7 100%)}}html.theme--catppuccin-mocha .hero.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-warning strong{color:inherit}html.theme--catppuccin-mocha .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-warning .navbar-menu{background-color:#f9e2af}}html.theme--catppuccin-mocha .hero.is-warning .navbar-item,html.theme--catppuccin-mocha .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-warning .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-warning .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-warning .tabs li.is-active a{color:#f9e2af !important;opacity:1}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #fcbd79 0%, #f9e2af 71%, #fcf4c5 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #fcbd79 0%, #f9e2af 71%, #fcf4c5 100%)}}html.theme--catppuccin-mocha .hero.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-danger strong{color:inherit}html.theme--catppuccin-mocha .hero.is-danger .title{color:#fff}html.theme--catppuccin-mocha .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-danger .navbar-menu{background-color:#f38ba8}}html.theme--catppuccin-mocha .hero.is-danger .navbar-item,html.theme--catppuccin-mocha .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-danger .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-danger .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-danger .tabs li.is-active a{color:#f38ba8 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #f7549d 0%, #f38ba8 71%, #f8a0a9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f7549d 0%, #f38ba8 71%, #f8a0a9 100%)}}html.theme--catppuccin-mocha .hero.is-small .hero-body,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-mocha .hero.is-halfheight .hero-body,html.theme--catppuccin-mocha .hero.is-fullheight .hero-body,html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-mocha .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-mocha .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-mocha .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-mocha .hero-video{overflow:hidden}html.theme--catppuccin-mocha .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-mocha .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero-video{display:none}}html.theme--catppuccin-mocha .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero-buttons .button{display:flex}html.theme--catppuccin-mocha .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-mocha .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-mocha .hero-head,html.theme--catppuccin-mocha .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero-body{padding:3rem 3rem}}html.theme--catppuccin-mocha .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .section{padding:3rem 3rem}html.theme--catppuccin-mocha .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-mocha .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-mocha .footer{background-color:#181825;padding:3rem 1.5rem 6rem}html.theme--catppuccin-mocha h1 .docs-heading-anchor,html.theme--catppuccin-mocha h1 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h1 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h2 .docs-heading-anchor,html.theme--catppuccin-mocha h2 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h2 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h3 .docs-heading-anchor,html.theme--catppuccin-mocha h3 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h3 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h4 .docs-heading-anchor,html.theme--catppuccin-mocha h4 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h4 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h5 .docs-heading-anchor,html.theme--catppuccin-mocha h5 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h5 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h6 .docs-heading-anchor,html.theme--catppuccin-mocha h6 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h6 .docs-heading-anchor:visited{color:#cdd6f4}html.theme--catppuccin-mocha h1 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h2 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h3 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h4 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h5 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-mocha h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-mocha h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-mocha .docs-light-only{display:none !important}html.theme--catppuccin-mocha pre{position:relative;overflow:hidden}html.theme--catppuccin-mocha pre code,html.theme--catppuccin-mocha pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-mocha pre code:first-of-type,html.theme--catppuccin-mocha pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-mocha pre code:last-of-type,html.theme--catppuccin-mocha pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-mocha pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#cdd6f4;cursor:pointer;text-align:center}html.theme--catppuccin-mocha pre .copy-button:focus,html.theme--catppuccin-mocha pre .copy-button:hover{opacity:1;background:rgba(205,214,244,0.1);color:#89b4fa}html.theme--catppuccin-mocha pre .copy-button.success{color:#a6e3a1;opacity:1}html.theme--catppuccin-mocha pre .copy-button.error{color:#f38ba8;opacity:1}html.theme--catppuccin-mocha pre:hover .copy-button{opacity:1}html.theme--catppuccin-mocha .link-icon:hover{color:#89b4fa}html.theme--catppuccin-mocha .admonition{background-color:#181825;border-style:solid;border-width:2px;border-color:#bac2de;border-radius:4px;font-size:1rem}html.theme--catppuccin-mocha .admonition strong{color:currentColor}html.theme--catppuccin-mocha .admonition.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-mocha .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .admonition.is-default{background-color:#181825;border-color:#bac2de}html.theme--catppuccin-mocha .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#bac2de}html.theme--catppuccin-mocha .admonition.is-default>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-info{background-color:#181825;border-color:#94e2d5}html.theme--catppuccin-mocha .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#94e2d5}html.theme--catppuccin-mocha .admonition.is-info>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-success{background-color:#181825;border-color:#a6e3a1}html.theme--catppuccin-mocha .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6e3a1}html.theme--catppuccin-mocha .admonition.is-success>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-warning{background-color:#181825;border-color:#f9e2af}html.theme--catppuccin-mocha .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#f9e2af}html.theme--catppuccin-mocha .admonition.is-warning>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-danger{background-color:#181825;border-color:#f38ba8}html.theme--catppuccin-mocha .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#f38ba8}html.theme--catppuccin-mocha .admonition.is-danger>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-compat{background-color:#181825;border-color:#89dceb}html.theme--catppuccin-mocha .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#89dceb}html.theme--catppuccin-mocha .admonition.is-compat>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-todo{background-color:#181825;border-color:#cba6f7}html.theme--catppuccin-mocha .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#cba6f7}html.theme--catppuccin-mocha .admonition.is-todo>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition-header{color:#bac2de;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-mocha .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-mocha .admonition-header .admonition-anchor{opacity:0;margin-left:0.5em;font-size:0.75em;color:inherit;text-decoration:none;transition:opacity 0.2s ease-in-out}html.theme--catppuccin-mocha .admonition-header .admonition-anchor:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-mocha .admonition-header .admonition-anchor:hover{opacity:1 !important;text-decoration:none}html.theme--catppuccin-mocha .admonition-header:hover .admonition-anchor{opacity:0.8}html.theme--catppuccin-mocha details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-mocha details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-mocha details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-mocha .admonition-body{color:#cdd6f4;padding:0.5rem .75rem}html.theme--catppuccin-mocha .admonition-body pre{background-color:#181825}html.theme--catppuccin-mocha .admonition-body code{background-color:#181825}html.theme--catppuccin-mocha details.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #585b70;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-mocha details.docstring>summary{list-style-type:none;align-items:stretch;padding:0.5rem .75rem;background-color:#181825;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #585b70;overflow:auto}html.theme--catppuccin-mocha details.docstring>summary code{background-color:transparent}html.theme--catppuccin-mocha details.docstring>summary .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-mocha details.docstring>summary .docstring-binding{margin-right:0.3em}html.theme--catppuccin-mocha details.docstring>summary .docstring-category{margin-left:0.3em}html.theme--catppuccin-mocha details.docstring>summary::before{content:'\f054';font-family:"Font Awesome 6 Free";font-weight:900;min-width:1.1rem;color:#2E63BD;display:inline-block}html.theme--catppuccin-mocha details.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #585b70}html.theme--catppuccin-mocha details.docstring>section:last-child{border-bottom:none}html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-mocha details.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-mocha details.docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-mocha details.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-mocha details.docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-mocha details.docstring[open]>summary::before{content:"\f078"}html.theme--catppuccin-mocha .documenter-example-output{background-color:#1e1e2e}html.theme--catppuccin-mocha .warning-overlay-base,html.theme--catppuccin-mocha .dev-warning-overlay,html.theme--catppuccin-mocha .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-mocha .warning-overlay-base .outdated-warning-closer,html.theme--catppuccin-mocha .dev-warning-overlay .outdated-warning-closer,html.theme--catppuccin-mocha .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-mocha .warning-overlay-base a,html.theme--catppuccin-mocha .dev-warning-overlay a,html.theme--catppuccin-mocha .outdated-warning-overlay a{color:#89b4fa}html.theme--catppuccin-mocha .warning-overlay-base a:hover,html.theme--catppuccin-mocha .dev-warning-overlay a:hover,html.theme--catppuccin-mocha .outdated-warning-overlay a:hover{color:#89dceb}html.theme--catppuccin-mocha .outdated-warning-overlay{background-color:#181825;color:#cdd6f4;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-mocha .dev-warning-overlay{background-color:#181825;color:#cdd6f4;border-bottom:3px solid rgba(0,0,0,0)}html.theme--catppuccin-mocha .footnote-reference{position:relative;display:inline-block}html.theme--catppuccin-mocha .footnote-preview{display:none;position:absolute;z-index:1000;max-width:300px;width:max-content;background-color:#1e1e2e;border:1px solid #89dceb;padding:10px;border-radius:5px;top:calc(100% + 10px);left:50%;transform:translateX(-50%);box-sizing:border-box;--arrow-left: 50%}html.theme--catppuccin-mocha .footnote-preview::before{content:"";position:absolute;top:-10px;left:var(--arrow-left);transform:translateX(-50%);border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #89dceb}html.theme--catppuccin-mocha .content pre{border:2px solid #585b70;border-radius:4px}html.theme--catppuccin-mocha .content code{font-weight:inherit}html.theme--catppuccin-mocha .content a code{color:#89b4fa}html.theme--catppuccin-mocha .content a:hover code{color:#89dceb}html.theme--catppuccin-mocha .content h1 code,html.theme--catppuccin-mocha .content h2 code,html.theme--catppuccin-mocha .content h3 code,html.theme--catppuccin-mocha .content h4 code,html.theme--catppuccin-mocha .content h5 code,html.theme--catppuccin-mocha .content h6 code{color:#cdd6f4}html.theme--catppuccin-mocha .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-mocha .content blockquote>ul:first-child,html.theme--catppuccin-mocha .content blockquote>ol:first-child,html.theme--catppuccin-mocha .content .admonition-body>ul:first-child,html.theme--catppuccin-mocha .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-mocha pre,html.theme--catppuccin-mocha code{font-variant-ligatures:no-contextual}html.theme--catppuccin-mocha .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-mocha .breadcrumb a.is-disabled,html.theme--catppuccin-mocha .breadcrumb a.is-disabled:hover{color:#b8c5ef}html.theme--catppuccin-mocha .hljs{background:initial !important}html.theme--catppuccin-mocha .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-mocha .katex-display,html.theme--catppuccin-mocha mjx-container,html.theme--catppuccin-mocha .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-mocha html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-mocha li.no-marker{list-style:none}html.theme--catppuccin-mocha #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-mocha #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main{width:100%}html.theme--catppuccin-mocha #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-mocha #documenter .docs-main>header,html.theme--catppuccin-mocha #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar{background-color:#1e1e2e;border-bottom:1px solid #585b70;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow:hidden}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-mocha #documenter .docs-main section.footnotes{border-top:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li details.docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-mocha .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #585b70;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-mocha #documenter .docs-sidebar{display:flex;flex-direction:column;color:#cdd6f4;background-color:#181825;border-right:1px solid #585b70;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-mocha #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name a:hover{color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #585b70;display:none;padding:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #585b70;padding-bottom:1.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#cdd6f4;background:#181825}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#cdd6f4;background-color:#202031}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #585b70;border-bottom:1px solid #585b70;background-color:#11111b}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#11111b;color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#202031;color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"โšฌ";margin-right:0.4em}html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-mocha #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#28283e}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#383856}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#28283e}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#383856}}html.theme--catppuccin-mocha kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-mocha .search-min-width-50{min-width:50%}html.theme--catppuccin-mocha .search-min-height-100{min-height:100%}html.theme--catppuccin-mocha .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-mocha .search-result-link{border-radius:0.7em;transition:all 300ms;border:1px solid transparent}html.theme--catppuccin-mocha .search-result-link:hover,html.theme--catppuccin-mocha .search-result-link:focus{background-color:rgba(0,128,128,0.1);outline:none;border-color:#94e2d5}html.theme--catppuccin-mocha .search-result-link .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-mocha .property-search-result-badge,html.theme--catppuccin-mocha .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-mocha .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:hover .search-filter,html.theme--catppuccin-mocha .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-mocha .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-mocha .search-filter:hover,html.theme--catppuccin-mocha .search-filter:focus{color:#333}html.theme--catppuccin-mocha .search-filter-selected{color:#313244;background-color:#b4befe}html.theme--catppuccin-mocha .search-filter-selected:hover,html.theme--catppuccin-mocha .search-filter-selected:focus{color:#313244}html.theme--catppuccin-mocha .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-mocha .search-divider{border-bottom:1px solid #585b70}html.theme--catppuccin-mocha .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-mocha .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-mocha .w-100{width:100%}html.theme--catppuccin-mocha .gap-2{gap:0.5rem}html.theme--catppuccin-mocha .gap-4{gap:1rem}html.theme--catppuccin-mocha .gap-8{gap:2rem}html.theme--catppuccin-mocha{background-color:#1e1e2e;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-mocha a{transition:all 200ms ease}html.theme--catppuccin-mocha .label{color:#cdd6f4}html.theme--catppuccin-mocha .button,html.theme--catppuccin-mocha .control.has-icons-left .icon,html.theme--catppuccin-mocha .control.has-icons-right .icon,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .select,html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea{height:2.5em;color:#cdd6f4}html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#cdd6f4}html.theme--catppuccin-mocha .select:after,html.theme--catppuccin-mocha .select select{border-width:1px}html.theme--catppuccin-mocha .menu-list a{transition:all 300ms ease}html.theme--catppuccin-mocha .modal-card-foot,html.theme--catppuccin-mocha .modal-card-head{border-color:#585b70}html.theme--catppuccin-mocha .navbar{border-radius:.4em}html.theme--catppuccin-mocha .navbar.is-transparent{background:none}html.theme--catppuccin-mocha .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .navbar .navbar-menu{background-color:#89b4fa;border-radius:0 0 .4em .4em}}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){color:#313244}html.theme--catppuccin-mocha .tag.is-link:not(body),html.theme--catppuccin-mocha details.docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-mocha .content kbd.is-link:not(body){color:#313244}html.theme--catppuccin-mocha .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-mocha .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-mocha .ansi span.sgr3{font-style:italic}html.theme--catppuccin-mocha .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-mocha .ansi span.sgr7{color:#1e1e2e;background-color:#cdd6f4}html.theme--catppuccin-mocha .ansi span.sgr8{color:transparent}html.theme--catppuccin-mocha .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-mocha .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-mocha .ansi span.sgr30{color:#45475a}html.theme--catppuccin-mocha .ansi span.sgr31{color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr32{color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr33{color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr34{color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr35{color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr36{color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr37{color:#bac2de}html.theme--catppuccin-mocha .ansi span.sgr40{background-color:#45475a}html.theme--catppuccin-mocha .ansi span.sgr41{background-color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr42{background-color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr43{background-color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr44{background-color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr45{background-color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr46{background-color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr47{background-color:#bac2de}html.theme--catppuccin-mocha .ansi span.sgr90{color:#585b70}html.theme--catppuccin-mocha .ansi span.sgr91{color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr92{color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr93{color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr94{color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr95{color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr96{color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr97{color:#a6adc8}html.theme--catppuccin-mocha .ansi span.sgr100{background-color:#585b70}html.theme--catppuccin-mocha .ansi span.sgr101{background-color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr102{background-color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr103{background-color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr104{background-color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr105{background-color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr106{background-color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr107{background-color:#a6adc8}html.theme--catppuccin-mocha code.language-julia-repl>span.hljs-meta{color:#a6e3a1;font-weight:bolder}html.theme--catppuccin-mocha code .hljs{color:#cdd6f4;background:#1e1e2e}html.theme--catppuccin-mocha code .hljs-keyword{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-built_in{color:#f38ba8}html.theme--catppuccin-mocha code .hljs-type{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-literal{color:#fab387}html.theme--catppuccin-mocha code .hljs-number{color:#fab387}html.theme--catppuccin-mocha code .hljs-operator{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-punctuation{color:#bac2de}html.theme--catppuccin-mocha code .hljs-property{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-regexp{color:#f5c2e7}html.theme--catppuccin-mocha code .hljs-string{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-char.escape_{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-subst{color:#a6adc8}html.theme--catppuccin-mocha code .hljs-symbol{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-variable{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-variable.language_{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-variable.constant_{color:#fab387}html.theme--catppuccin-mocha code .hljs-title{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-title.class_{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-title.function_{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-params{color:#cdd6f4}html.theme--catppuccin-mocha code .hljs-comment{color:#585b70}html.theme--catppuccin-mocha code .hljs-doctag{color:#f38ba8}html.theme--catppuccin-mocha code .hljs-meta{color:#fab387}html.theme--catppuccin-mocha code .hljs-section{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-tag{color:#a6adc8}html.theme--catppuccin-mocha code .hljs-name{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-attr{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-attribute{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-bullet{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-code{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-emphasis{color:#f38ba8;font-style:italic}html.theme--catppuccin-mocha code .hljs-strong{color:#f38ba8;font-weight:bold}html.theme--catppuccin-mocha code .hljs-formula{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-link{color:#74c7ec;font-style:italic}html.theme--catppuccin-mocha code .hljs-quote{color:#a6e3a1;font-style:italic}html.theme--catppuccin-mocha code .hljs-selector-tag{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-selector-id{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-selector-class{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-selector-attr{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-selector-pseudo{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-template-tag{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-template-variable{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-addition{color:#a6e3a1;background:rgba(166,227,161,0.15)}html.theme--catppuccin-mocha code .hljs-deletion{color:#f38ba8;background:rgba(243,139,168,0.15)}html.theme--catppuccin-mocha .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover,html.theme--catppuccin-mocha .search-result-link:focus{background-color:#313244}html.theme--catppuccin-mocha .search-result-link .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:hover .search-filter,html.theme--catppuccin-mocha .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:focus .search-filter{color:#313244 !important;background-color:#b4befe !important}html.theme--catppuccin-mocha .search-result-title{color:#cdd6f4}html.theme--catppuccin-mocha .search-result-highlight{background-color:#f38ba8;color:#181825}html.theme--catppuccin-mocha .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-mocha .w-100{width:100%}html.theme--catppuccin-mocha .gap-2{gap:0.5rem}html.theme--catppuccin-mocha .gap-4{gap:1rem} diff --git a/save/docs/build/assets/themes/documenter-dark.css b/save/docs/build/assets/themes/documenter-dark.css new file mode 100644 index 00000000..287c0be1 --- /dev/null +++ b/save/docs/build/assets/themes/documenter-dark.css @@ -0,0 +1,7 @@ +๏ปฟhtml.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus,html.theme--documenter-dark .pagination-ellipsis:focus,html.theme--documenter-dark .file-cta:focus,html.theme--documenter-dark .file-name:focus,html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .button:focus,html.theme--documenter-dark .is-focused.pagination-previous,html.theme--documenter-dark .is-focused.pagination-next,html.theme--documenter-dark .is-focused.pagination-link,html.theme--documenter-dark .is-focused.pagination-ellipsis,html.theme--documenter-dark .is-focused.file-cta,html.theme--documenter-dark .is-focused.file-name,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-focused.button,html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active,html.theme--documenter-dark .pagination-ellipsis:active,html.theme--documenter-dark .file-cta:active,html.theme--documenter-dark .file-name:active,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .button:active,html.theme--documenter-dark .is-active.pagination-previous,html.theme--documenter-dark .is-active.pagination-next,html.theme--documenter-dark .is-active.pagination-link,html.theme--documenter-dark .is-active.pagination-ellipsis,html.theme--documenter-dark .is-active.file-cta,html.theme--documenter-dark .is-active.file-name,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .is-active.button{outline:none}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-ellipsis[disabled],html.theme--documenter-dark .file-cta[disabled],html.theme--documenter-dark .file-name[disabled],html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--documenter-dark .pagination-next,html.theme--documenter-dark fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--documenter-dark .pagination-link,html.theme--documenter-dark fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--documenter-dark .file-cta,html.theme--documenter-dark fieldset[disabled] .file-cta,fieldset[disabled] html.theme--documenter-dark .file-name,html.theme--documenter-dark fieldset[disabled] .file-name,fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark fieldset[disabled] .select select,html.theme--documenter-dark .select fieldset[disabled] select,html.theme--documenter-dark fieldset[disabled] .textarea,html.theme--documenter-dark fieldset[disabled] .input,html.theme--documenter-dark fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--documenter-dark .button,html.theme--documenter-dark fieldset[disabled] .button{cursor:not-allowed}html.theme--documenter-dark .tabs,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .breadcrumb,html.theme--documenter-dark .file,html.theme--documenter-dark .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after,html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--documenter-dark .admonition:not(:last-child),html.theme--documenter-dark .tabs:not(:last-child),html.theme--documenter-dark .pagination:not(:last-child),html.theme--documenter-dark .message:not(:last-child),html.theme--documenter-dark .level:not(:last-child),html.theme--documenter-dark .breadcrumb:not(:last-child),html.theme--documenter-dark .block:not(:last-child),html.theme--documenter-dark .title:not(:last-child),html.theme--documenter-dark .subtitle:not(:last-child),html.theme--documenter-dark .table-container:not(:last-child),html.theme--documenter-dark .table:not(:last-child),html.theme--documenter-dark .progress:not(:last-child),html.theme--documenter-dark .notification:not(:last-child),html.theme--documenter-dark .content:not(:last-child),html.theme--documenter-dark .box:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .modal-close,html.theme--documenter-dark .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before,html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before{height:2px;width:50%}html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{height:50%;width:2px}html.theme--documenter-dark .modal-close:hover,html.theme--documenter-dark .delete:hover,html.theme--documenter-dark .modal-close:focus,html.theme--documenter-dark .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--documenter-dark .modal-close:active,html.theme--documenter-dark .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--documenter-dark .is-small.modal-close,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--documenter-dark .is-small.delete,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--documenter-dark .is-medium.modal-close,html.theme--documenter-dark .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--documenter-dark .is-large.modal-close,html.theme--documenter-dark .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--documenter-dark .control.is-loading::after,html.theme--documenter-dark .select.is-loading::after,html.theme--documenter-dark .loader,html.theme--documenter-dark .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdee0;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--documenter-dark .hero-video,html.theme--documenter-dark .modal-background,html.theme--documenter-dark .modal,html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--documenter-dark .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#ecf0f1 !important}a.has-text-light:hover,a.has-text-light:focus{color:#cfd9db !important}.has-background-light{background-color:#ecf0f1 !important}.has-text-dark{color:#282f2f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#111414 !important}.has-background-dark{background-color:#282f2f !important}.has-text-primary{color:#375a7f !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#28415b !important}.has-background-primary{background-color:#375a7f !important}.has-text-primary-light{color:#f1f5f9 !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#cddbe9 !important}.has-background-primary-light{background-color:#f1f5f9 !important}.has-text-primary-dark{color:#4d7eb2 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#7198c1 !important}.has-background-primary-dark{background-color:#4d7eb2 !important}.has-text-link{color:#1abc9c !important}a.has-text-link:hover,a.has-text-link:focus{color:#148f77 !important}.has-background-link{background-color:#1abc9c !important}.has-text-link-light{color:#edfdf9 !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c0f6ec !important}.has-background-link-light{background-color:#edfdf9 !important}.has-text-link-dark{color:#15987e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1bc5a4 !important}.has-background-link-dark{background-color:#15987e !important}.has-text-info{color:#3c5dcd !important}a.has-text-info:hover,a.has-text-info:focus{color:#2c48aa !important}.has-background-info{background-color:#3c5dcd !important}.has-text-info-light{color:#eff2fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6d0f0 !important}.has-background-info-light{background-color:#eff2fb !important}.has-text-info-dark{color:#3253c3 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#5571d3 !important}.has-background-info-dark{background-color:#3253c3 !important}.has-text-success{color:#259a12 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a6c0d !important}.has-background-success{background-color:#259a12 !important}.has-text-success-light{color:#effded !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c7f8bf !important}.has-background-success-light{background-color:#effded !important}.has-text-success-dark{color:#2ec016 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#3fe524 !important}.has-background-success-dark{background-color:#2ec016 !important}.has-text-warning{color:#f4c72f !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#e4b30c !important}.has-background-warning{background-color:#f4c72f !important}.has-text-warning-light{color:#fefaec !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fbedbb !important}.has-background-warning-light{background-color:#fefaec !important}.has-text-warning-dark{color:#8c6e07 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#bd940a !important}.has-background-warning-dark{background-color:#8c6e07 !important}.has-text-danger{color:#cb3c33 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a23029 !important}.has-background-danger{background-color:#cb3c33 !important}.has-text-danger-light{color:#fbefef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f1c8c6 !important}.has-background-danger-light{background-color:#fbefef !important}.has-text-danger-dark{color:#c03930 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#d35850 !important}.has-background-danger-dark{background-color:#c03930 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#282f2f !important}.has-background-grey-darker{background-color:#282f2f !important}.has-text-grey-dark{color:#343c3d !important}.has-background-grey-dark{background-color:#343c3d !important}.has-text-grey{color:#5e6d6f !important}.has-background-grey{background-color:#5e6d6f !important}.has-text-grey-light{color:#8c9b9d !important}.has-background-grey-light{background-color:#8c9b9d !important}.has-text-grey-lighter{color:#dbdee0 !important}.has-background-grey-lighter{background-color:#dbdee0 !important}.has-text-white-ter{color:#ecf0f1 !important}.has-background-white-ter{background-color:#ecf0f1 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--documenter-dark{/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/}html.theme--documenter-dark html{background-color:#1f2424;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark article,html.theme--documenter-dark aside,html.theme--documenter-dark figure,html.theme--documenter-dark footer,html.theme--documenter-dark header,html.theme--documenter-dark hgroup,html.theme--documenter-dark section{display:block}html.theme--documenter-dark body,html.theme--documenter-dark button,html.theme--documenter-dark input,html.theme--documenter-dark optgroup,html.theme--documenter-dark select,html.theme--documenter-dark textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--documenter-dark code,html.theme--documenter-dark pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark body{color:#fff;font-size:1em;font-weight:400;line-height:1.5}html.theme--documenter-dark a{color:#1abc9c;cursor:pointer;text-decoration:none}html.theme--documenter-dark a strong{color:currentColor}html.theme--documenter-dark a:hover{color:#1dd2af}html.theme--documenter-dark code{background-color:rgba(255,255,255,0.05);color:#ececec;font-size:.875em;font-weight:normal;padding:.1em}html.theme--documenter-dark hr{background-color:#282f2f;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--documenter-dark img{height:auto;max-width:100%}html.theme--documenter-dark input[type="checkbox"],html.theme--documenter-dark input[type="radio"]{vertical-align:baseline}html.theme--documenter-dark small{font-size:.875em}html.theme--documenter-dark span{font-style:inherit;font-weight:inherit}html.theme--documenter-dark strong{color:#f2f2f2;font-weight:700}html.theme--documenter-dark fieldset{border:none}html.theme--documenter-dark pre{-webkit-overflow-scrolling:touch;background-color:#282f2f;color:#fff;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--documenter-dark pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--documenter-dark table td,html.theme--documenter-dark table th{vertical-align:top}html.theme--documenter-dark table td:not([align]),html.theme--documenter-dark table th:not([align]){text-align:inherit}html.theme--documenter-dark table th{color:#f2f2f2}html.theme--documenter-dark .box{background-color:#343c3d;border-radius:8px;box-shadow:none;color:#fff;display:block;padding:1.25rem}html.theme--documenter-dark a.box:hover,html.theme--documenter-dark a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1abc9c}html.theme--documenter-dark a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1abc9c}html.theme--documenter-dark .button{background-color:#282f2f;border-color:#4c5759;border-width:1px;color:#375a7f;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--documenter-dark .button strong{color:inherit}html.theme--documenter-dark .button .icon,html.theme--documenter-dark .button .icon.is-small,html.theme--documenter-dark .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--documenter-dark #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--documenter-dark .button .icon.is-medium,html.theme--documenter-dark .button .icon.is-large{height:1.5em;width:1.5em}html.theme--documenter-dark .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--documenter-dark .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button:hover,html.theme--documenter-dark .button.is-hovered{border-color:#8c9b9d;color:#f2f2f2}html.theme--documenter-dark .button:focus,html.theme--documenter-dark .button.is-focused{border-color:#8c9b9d;color:#17a689}html.theme--documenter-dark .button:focus:not(:active),html.theme--documenter-dark .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button:active,html.theme--documenter-dark .button.is-active{border-color:#343c3d;color:#f2f2f2}html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;color:#fff;text-decoration:underline}html.theme--documenter-dark .button.is-text:hover,html.theme--documenter-dark .button.is-text.is-hovered,html.theme--documenter-dark .button.is-text:focus,html.theme--documenter-dark .button.is-text.is-focused{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .button.is-text:active,html.theme--documenter-dark .button.is-text.is-active{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .button.is-text[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--documenter-dark .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1abc9c;text-decoration:none}html.theme--documenter-dark .button.is-ghost:hover,html.theme--documenter-dark .button.is-ghost.is-hovered{color:#1abc9c;text-decoration:underline}html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:hover,html.theme--documenter-dark .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus,html.theme--documenter-dark .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus:not(:active),html.theme--documenter-dark .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--documenter-dark .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-white.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:hover,html.theme--documenter-dark .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus,html.theme--documenter-dark .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus:not(:active),html.theme--documenter-dark .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:hover,html.theme--documenter-dark .button.is-light.is-hovered{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus,html.theme--documenter-dark .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus:not(:active),html.theme--documenter-dark .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light.is-active{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:#ecf0f1;box-shadow:none}html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-outlined.is-focused{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-dark,html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover,html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus:not(:active),html.theme--documenter-dark .content kbd.button:focus:not(:active),html.theme--documenter-dark .button.is-dark.is-focused:not(:active),html.theme--documenter-dark .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark[disabled],html.theme--documenter-dark .content kbd.button[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark,fieldset[disabled] html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:#282f2f;box-shadow:none}html.theme--documenter-dark .button.is-dark.is-inverted,html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted:hover,html.theme--documenter-dark .content kbd.button.is-inverted:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-dark.is-inverted[disabled],html.theme--documenter-dark .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-loading::after,html.theme--documenter-dark .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined,html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-outlined.is-focused{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark details.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark details.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus:not(:active),html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--documenter-dark .button.is-primary.is-focused:not(:active),html.theme--documenter-dark details.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark details.docstring>section>a.button.is-active.docs-sourcelink{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary[disabled],html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary,fieldset[disabled] html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;box-shadow:none}html.theme--documenter-dark .button.is-primary.is-inverted,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted:hover,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-hovered,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--documenter-dark .button.is-primary.is-inverted[disabled],html.theme--documenter-dark details.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted,fieldset[disabled] html.theme--documenter-dark details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-loading::after,html.theme--documenter-dark details.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-outlined:hover,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-outlined.is-hovered,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-outlined:focus,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-outlined.is-focused,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading::after,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:hover::after,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:focus::after,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark details.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined[disabled],html.theme--documenter-dark details.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-outlined,fieldset[disabled] html.theme--documenter-dark details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:hover,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:focus,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined[disabled],html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary.is-light,html.theme--documenter-dark details.docstring>section>a.button.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:hover,html.theme--documenter-dark details.docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-light.is-hovered,html.theme--documenter-dark details.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e8eef5;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:active,html.theme--documenter-dark details.docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-light.is-active,html.theme--documenter-dark details.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#dfe8f1;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:hover,html.theme--documenter-dark .button.is-link.is-hovered{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus,html.theme--documenter-dark .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus:not(:active),html.theme--documenter-dark .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link.is-active{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:#1abc9c;box-shadow:none}html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-outlined.is-focused{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:hover,html.theme--documenter-dark .button.is-link.is-light.is-hovered{background-color:#e2fbf6;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:active,html.theme--documenter-dark .button.is-link.is-light.is-active{background-color:#d7f9f3;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-info{background-color:#3c5dcd;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:hover,html.theme--documenter-dark .button.is-info.is-hovered{background-color:#3355c9;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus,html.theme--documenter-dark .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus:not(:active),html.theme--documenter-dark .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info.is-active{background-color:#3151bf;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info{background-color:#3c5dcd;border-color:#3c5dcd;box-shadow:none}html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-outlined.is-focused{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}html.theme--documenter-dark .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}html.theme--documenter-dark .button.is-info.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;box-shadow:none;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-info.is-light{background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .button.is-info.is-light:hover,html.theme--documenter-dark .button.is-info.is-light.is-hovered{background-color:#e5e9f8;border-color:transparent;color:#3253c3}html.theme--documenter-dark .button.is-info.is-light:active,html.theme--documenter-dark .button.is-info.is-light.is-active{background-color:#dae1f6;border-color:transparent;color:#3253c3}html.theme--documenter-dark .button.is-success{background-color:#259a12;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:hover,html.theme--documenter-dark .button.is-success.is-hovered{background-color:#228f11;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus,html.theme--documenter-dark .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus:not(:active),html.theme--documenter-dark .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success.is-active{background-color:#20830f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success{background-color:#259a12;border-color:#259a12;box-shadow:none}html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#259a12}html.theme--documenter-dark .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;color:#259a12}html.theme--documenter-dark .button.is-success.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-outlined.is-focused{background-color:#259a12;border-color:#259a12;color:#fff}html.theme--documenter-dark .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #259a12 #259a12 !important}html.theme--documenter-dark .button.is-success.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;box-shadow:none;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #259a12 #259a12 !important}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-success.is-light{background-color:#effded;color:#2ec016}html.theme--documenter-dark .button.is-success.is-light:hover,html.theme--documenter-dark .button.is-success.is-light.is-hovered{background-color:#e5fce1;border-color:transparent;color:#2ec016}html.theme--documenter-dark .button.is-success.is-light:active,html.theme--documenter-dark .button.is-success.is-light.is-active{background-color:#dbfad6;border-color:transparent;color:#2ec016}html.theme--documenter-dark .button.is-warning{background-color:#f4c72f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:hover,html.theme--documenter-dark .button.is-warning.is-hovered{background-color:#f3c423;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:focus,html.theme--documenter-dark .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:focus:not(:active),html.theme--documenter-dark .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning.is-active{background-color:#f3c017;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning{background-color:#f4c72f;border-color:#f4c72f;box-shadow:none}html.theme--documenter-dark .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#f4c72f;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-outlined.is-focused{background-color:#f4c72f;border-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #f4c72f #f4c72f !important}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#f4c72f;box-shadow:none;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f4c72f #f4c72f !important}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-light{background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .button.is-warning.is-light:hover,html.theme--documenter-dark .button.is-warning.is-light.is-hovered{background-color:#fdf7e0;border-color:transparent;color:#8c6e07}html.theme--documenter-dark .button.is-warning.is-light:active,html.theme--documenter-dark .button.is-warning.is-light.is-active{background-color:#fdf3d3;border-color:transparent;color:#8c6e07}html.theme--documenter-dark .button.is-danger{background-color:#cb3c33;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:hover,html.theme--documenter-dark .button.is-danger.is-hovered{background-color:#c13930;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus,html.theme--documenter-dark .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus:not(:active),html.theme--documenter-dark .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger.is-active{background-color:#b7362e;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger{background-color:#cb3c33;border-color:#cb3c33;box-shadow:none}html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-outlined.is-focused{background-color:#cb3c33;border-color:#cb3c33;color:#fff}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;box-shadow:none;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-danger.is-light{background-color:#fbefef;color:#c03930}html.theme--documenter-dark .button.is-danger.is-light:hover,html.theme--documenter-dark .button.is-danger.is-light.is-hovered{background-color:#f8e6e5;border-color:transparent;color:#c03930}html.theme--documenter-dark .button.is-danger.is-light:active,html.theme--documenter-dark .button.is-danger.is-light.is-active{background-color:#f6dcda;border-color:transparent;color:#c03930}html.theme--documenter-dark .button.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--documenter-dark .button.is-small:not(.is-rounded),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--documenter-dark .button.is-normal{font-size:1rem}html.theme--documenter-dark .button.is-medium{font-size:1.25rem}html.theme--documenter-dark .button.is-large{font-size:1.5rem}html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .button{background-color:#8c9b9d;border-color:#5e6d6f;box-shadow:none;opacity:.5}html.theme--documenter-dark .button.is-fullwidth{display:flex;width:100%}html.theme--documenter-dark .button.is-loading{color:transparent !important;pointer-events:none}html.theme--documenter-dark .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--documenter-dark .button.is-static{background-color:#282f2f;border-color:#5e6d6f;color:#dbdee0;box-shadow:none;pointer-events:none}html.theme--documenter-dark .button.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--documenter-dark .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .buttons .button{margin-bottom:0.5rem}html.theme--documenter-dark .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--documenter-dark .buttons:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .buttons:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--documenter-dark .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--documenter-dark .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--documenter-dark .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--documenter-dark .buttons.has-addons .button:last-child{margin-right:0}html.theme--documenter-dark .buttons.has-addons .button:hover,html.theme--documenter-dark .buttons.has-addons .button.is-hovered{z-index:2}html.theme--documenter-dark .buttons.has-addons .button:focus,html.theme--documenter-dark .buttons.has-addons .button.is-focused,html.theme--documenter-dark .buttons.has-addons .button:active,html.theme--documenter-dark .buttons.has-addons .button.is-active,html.theme--documenter-dark .buttons.has-addons .button.is-selected{z-index:3}html.theme--documenter-dark .buttons.has-addons .button:focus:hover,html.theme--documenter-dark .buttons.has-addons .button.is-focused:hover,html.theme--documenter-dark .buttons.has-addons .button:active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--documenter-dark .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .buttons.is-centered{justify-content:center}html.theme--documenter-dark .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--documenter-dark .buttons.is-right{justify-content:flex-end}html.theme--documenter-dark .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:1rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1.25rem}}html.theme--documenter-dark .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--documenter-dark .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--documenter-dark .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--documenter-dark .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--documenter-dark .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--documenter-dark .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--documenter-dark .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--documenter-dark .content li+li{margin-top:0.25em}html.theme--documenter-dark .content p:not(:last-child),html.theme--documenter-dark .content dl:not(:last-child),html.theme--documenter-dark .content ol:not(:last-child),html.theme--documenter-dark .content ul:not(:last-child),html.theme--documenter-dark .content blockquote:not(:last-child),html.theme--documenter-dark .content pre:not(:last-child),html.theme--documenter-dark .content table:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .content h1,html.theme--documenter-dark .content h2,html.theme--documenter-dark .content h3,html.theme--documenter-dark .content h4,html.theme--documenter-dark .content h5,html.theme--documenter-dark .content h6{color:#f2f2f2;font-weight:600;line-height:1.125}html.theme--documenter-dark .content h1{font-size:2em;margin-bottom:0.5em}html.theme--documenter-dark .content h1:not(:first-child){margin-top:1em}html.theme--documenter-dark .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--documenter-dark .content h2:not(:first-child){margin-top:1.1428em}html.theme--documenter-dark .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--documenter-dark .content h3:not(:first-child){margin-top:1.3333em}html.theme--documenter-dark .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--documenter-dark .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--documenter-dark .content h6{font-size:1em;margin-bottom:1em}html.theme--documenter-dark .content blockquote{background-color:#282f2f;border-left:5px solid #5e6d6f;padding:1.25em 1.5em}html.theme--documenter-dark .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ol:not([type]){list-style-type:decimal}html.theme--documenter-dark .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--documenter-dark .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--documenter-dark .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--documenter-dark .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--documenter-dark .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--documenter-dark .content ul ul ul{list-style-type:square}html.theme--documenter-dark .content dd{margin-left:2em}html.theme--documenter-dark .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--documenter-dark .content figure:not(:first-child){margin-top:2em}html.theme--documenter-dark .content figure:not(:last-child){margin-bottom:2em}html.theme--documenter-dark .content figure img{display:inline-block}html.theme--documenter-dark .content figure figcaption{font-style:italic}html.theme--documenter-dark .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--documenter-dark .content sup,html.theme--documenter-dark .content sub{font-size:75%}html.theme--documenter-dark .content table{width:100%}html.theme--documenter-dark .content table td,html.theme--documenter-dark .content table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .content table th{color:#f2f2f2}html.theme--documenter-dark .content table th:not([align]){text-align:inherit}html.theme--documenter-dark .content table thead td,html.theme--documenter-dark .content table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .content table tfoot td,html.theme--documenter-dark .content table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .content table tbody tr:last-child td,html.theme--documenter-dark .content table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .content .tabs li+li{margin-top:0}html.theme--documenter-dark .content.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--documenter-dark .content.is-normal{font-size:1rem}html.theme--documenter-dark .content.is-medium{font-size:1.25rem}html.theme--documenter-dark .content.is-large{font-size:1.5rem}html.theme--documenter-dark .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--documenter-dark .icon.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--documenter-dark .icon.is-medium{height:2rem;width:2rem}html.theme--documenter-dark .icon.is-large{height:3rem;width:3rem}html.theme--documenter-dark .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--documenter-dark .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--documenter-dark .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--documenter-dark div.icon-text{display:flex}html.theme--documenter-dark .image,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--documenter-dark .image img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--documenter-dark .image img.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--documenter-dark .image.is-fullwidth,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--documenter-dark .image.is-square,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--documenter-dark .image.is-1by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--documenter-dark .image.is-5by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--documenter-dark .image.is-4by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--documenter-dark .image.is-3by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--documenter-dark .image.is-5by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--documenter-dark .image.is-16by9,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--documenter-dark .image.is-2by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--documenter-dark .image.is-3by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--documenter-dark .image.is-4by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--documenter-dark .image.is-3by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--documenter-dark .image.is-2by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--documenter-dark .image.is-3by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--documenter-dark .image.is-9by16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--documenter-dark .image.is-1by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--documenter-dark .image.is-1by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--documenter-dark .image.is-16x16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--documenter-dark .image.is-24x24,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--documenter-dark .image.is-32x32,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--documenter-dark .image.is-48x48,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--documenter-dark .image.is-64x64,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--documenter-dark .image.is-96x96,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--documenter-dark .image.is-128x128,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--documenter-dark .notification{background-color:#282f2f;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--documenter-dark .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .notification strong{color:currentColor}html.theme--documenter-dark .notification code,html.theme--documenter-dark .notification pre{background:#fff}html.theme--documenter-dark .notification pre code{background:transparent}html.theme--documenter-dark .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--documenter-dark .notification .title,html.theme--documenter-dark .notification .subtitle,html.theme--documenter-dark .notification .content{color:currentColor}html.theme--documenter-dark .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .notification.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-dark,html.theme--documenter-dark .content kbd.notification{background-color:#282f2f;color:#fff}html.theme--documenter-dark .notification.is-primary,html.theme--documenter-dark details.docstring>section>a.notification.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .notification.is-primary.is-light,html.theme--documenter-dark details.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .notification.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .notification.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .notification.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .notification.is-info.is-light{background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .notification.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .notification.is-success.is-light{background-color:#effded;color:#2ec016}html.theme--documenter-dark .notification.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-warning.is-light{background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .notification.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .notification.is-danger.is-light{background-color:#fbefef;color:#c03930}html.theme--documenter-dark .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--documenter-dark .progress::-webkit-progress-bar{background-color:#343c3d}html.theme--documenter-dark .progress::-webkit-progress-value{background-color:#dbdee0}html.theme--documenter-dark .progress::-moz-progress-bar{background-color:#dbdee0}html.theme--documenter-dark .progress::-ms-fill{background-color:#dbdee0;border:none}html.theme--documenter-dark .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--documenter-dark .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--documenter-dark .progress.is-white::-ms-fill{background-color:#fff}html.theme--documenter-dark .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-light::-webkit-progress-value{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-moz-progress-bar{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-ms-fill{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light:indeterminate{background-image:linear-gradient(to right, #ecf0f1 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-dark::-webkit-progress-value,html.theme--documenter-dark .content kbd.progress::-webkit-progress-value{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-moz-progress-bar,html.theme--documenter-dark .content kbd.progress::-moz-progress-bar{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-ms-fill,html.theme--documenter-dark .content kbd.progress::-ms-fill{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark:indeterminate,html.theme--documenter-dark .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #282f2f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-primary::-webkit-progress-value,html.theme--documenter-dark details.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-moz-progress-bar,html.theme--documenter-dark details.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-ms-fill,html.theme--documenter-dark details.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary:indeterminate,html.theme--documenter-dark details.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #375a7f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-link::-webkit-progress-value{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-moz-progress-bar{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-ms-fill{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1abc9c 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-info::-webkit-progress-value{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info::-moz-progress-bar{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info::-ms-fill{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info:indeterminate{background-image:linear-gradient(to right, #3c5dcd 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-success::-webkit-progress-value{background-color:#259a12}html.theme--documenter-dark .progress.is-success::-moz-progress-bar{background-color:#259a12}html.theme--documenter-dark .progress.is-success::-ms-fill{background-color:#259a12}html.theme--documenter-dark .progress.is-success:indeterminate{background-image:linear-gradient(to right, #259a12 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-warning::-webkit-progress-value{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning::-moz-progress-bar{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning::-ms-fill{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #f4c72f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-danger::-webkit-progress-value{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger::-moz-progress-bar{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger::-ms-fill{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #cb3c33 30%, #343c3d 30%)}html.theme--documenter-dark .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#343c3d;background-image:linear-gradient(to right, #fff 30%, #343c3d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--documenter-dark .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-ms-fill{animation-name:none}html.theme--documenter-dark .progress.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--documenter-dark .progress.is-medium{height:1.25rem}html.theme--documenter-dark .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--documenter-dark .table{background-color:#343c3d;color:#fff}html.theme--documenter-dark .table td,html.theme--documenter-dark .table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .table td.is-white,html.theme--documenter-dark .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .table td.is-black,html.theme--documenter-dark .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .table td.is-light,html.theme--documenter-dark .table th.is-light{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-dark,html.theme--documenter-dark .table th.is-dark{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .table td.is-primary,html.theme--documenter-dark .table th.is-primary{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-link,html.theme--documenter-dark .table th.is-link{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .table td.is-info,html.theme--documenter-dark .table th.is-info{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}html.theme--documenter-dark .table td.is-success,html.theme--documenter-dark .table th.is-success{background-color:#259a12;border-color:#259a12;color:#fff}html.theme--documenter-dark .table td.is-warning,html.theme--documenter-dark .table th.is-warning{background-color:#f4c72f;border-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-danger,html.theme--documenter-dark .table th.is-danger{background-color:#cb3c33;border-color:#cb3c33;color:#fff}html.theme--documenter-dark .table td.is-narrow,html.theme--documenter-dark .table th.is-narrow{white-space:nowrap;width:1%}html.theme--documenter-dark .table td.is-selected,html.theme--documenter-dark .table th.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-selected a,html.theme--documenter-dark .table td.is-selected strong,html.theme--documenter-dark .table th.is-selected a,html.theme--documenter-dark .table th.is-selected strong{color:currentColor}html.theme--documenter-dark .table td.is-vcentered,html.theme--documenter-dark .table th.is-vcentered{vertical-align:middle}html.theme--documenter-dark .table th{color:#f2f2f2}html.theme--documenter-dark .table th:not([align]){text-align:left}html.theme--documenter-dark .table tr.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table tr.is-selected a,html.theme--documenter-dark .table tr.is-selected strong{color:currentColor}html.theme--documenter-dark .table tr.is-selected td,html.theme--documenter-dark .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--documenter-dark .table thead{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table thead td,html.theme--documenter-dark .table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .table tfoot{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tfoot td,html.theme--documenter-dark .table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .table tbody{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tbody tr:last-child td,html.theme--documenter-dark .table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .table.is-bordered td,html.theme--documenter-dark .table.is-bordered th{border-width:1px}html.theme--documenter-dark .table.is-bordered tr:last-child td,html.theme--documenter-dark .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--documenter-dark .table.is-fullwidth{width:100%}html.theme--documenter-dark .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#2d3435}html.theme--documenter-dark .table.is-narrow td,html.theme--documenter-dark .table.is-narrow th{padding:0.25em 0.5em}html.theme--documenter-dark .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#282f2f}html.theme--documenter-dark .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--documenter-dark .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .tags .tag,html.theme--documenter-dark .tags .content kbd,html.theme--documenter-dark .content .tags kbd,html.theme--documenter-dark .tags details.docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--documenter-dark .tags .tag:not(:last-child),html.theme--documenter-dark .tags .content kbd:not(:last-child),html.theme--documenter-dark .content .tags kbd:not(:last-child),html.theme--documenter-dark .tags details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--documenter-dark .tags:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .tags:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--documenter-dark .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--documenter-dark .tags.is-centered{justify-content:center}html.theme--documenter-dark .tags.is-centered .tag,html.theme--documenter-dark .tags.is-centered .content kbd,html.theme--documenter-dark .content .tags.is-centered kbd,html.theme--documenter-dark .tags.is-centered details.docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--documenter-dark .tags.is-right{justify-content:flex-end}html.theme--documenter-dark .tags.is-right .tag:not(:first-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:first-child),html.theme--documenter-dark .tags.is-right details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--documenter-dark .tags.is-right .tag:not(:last-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:last-child),html.theme--documenter-dark .tags.is-right details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--documenter-dark .tags.has-addons .tag,html.theme--documenter-dark .tags.has-addons .content kbd,html.theme--documenter-dark .content .tags.has-addons kbd,html.theme--documenter-dark .tags.has-addons details.docstring>section>a.docs-sourcelink{margin-right:0}html.theme--documenter-dark .tags.has-addons .tag:not(:first-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:first-child),html.theme--documenter-dark .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--documenter-dark .tags.has-addons .tag:not(:last-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:last-child),html.theme--documenter-dark .tags.has-addons details.docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--documenter-dark .tag:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#282f2f;border-radius:.4em;color:#fff;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--documenter-dark .tag:not(body) .delete,html.theme--documenter-dark .content kbd:not(body) .delete,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--documenter-dark .tag.is-white:not(body),html.theme--documenter-dark .content kbd.is-white:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .tag.is-black:not(body),html.theme--documenter-dark .content kbd.is-black:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .tag.is-light:not(body),html.theme--documenter-dark .content kbd.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-dark:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--documenter-dark .content details.docstring>section>kbd:not(body){background-color:#282f2f;color:#fff}html.theme--documenter-dark .tag.is-primary:not(body),html.theme--documenter-dark .content kbd.is-primary:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:not(body){background-color:#375a7f;color:#fff}html.theme--documenter-dark .tag.is-primary.is-light:not(body),html.theme--documenter-dark .content kbd.is-primary.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .tag.is-link:not(body),html.theme--documenter-dark .content kbd.is-link:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1abc9c;color:#fff}html.theme--documenter-dark .tag.is-link.is-light:not(body),html.theme--documenter-dark .content kbd.is-link.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .tag.is-info:not(body),html.theme--documenter-dark .content kbd.is-info:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .tag.is-info.is-light:not(body),html.theme--documenter-dark .content kbd.is-info.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .tag.is-success:not(body),html.theme--documenter-dark .content kbd.is-success:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#259a12;color:#fff}html.theme--documenter-dark .tag.is-success.is-light:not(body),html.theme--documenter-dark .content kbd.is-success.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#effded;color:#2ec016}html.theme--documenter-dark .tag.is-warning:not(body),html.theme--documenter-dark .content kbd.is-warning:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-warning.is-light:not(body),html.theme--documenter-dark .content kbd.is-warning.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .tag.is-danger:not(body),html.theme--documenter-dark .content kbd.is-danger:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#cb3c33;color:#fff}html.theme--documenter-dark .tag.is-danger.is-light:not(body),html.theme--documenter-dark .content kbd.is-danger.is-light:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fbefef;color:#c03930}html.theme--documenter-dark .tag.is-normal:not(body),html.theme--documenter-dark .content kbd.is-normal:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--documenter-dark .tag.is-medium:not(body),html.theme--documenter-dark .content kbd.is-medium:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--documenter-dark .tag.is-large:not(body),html.theme--documenter-dark .content kbd.is-large:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--documenter-dark .tag:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--documenter-dark .tag:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--documenter-dark .tag:not(body) .icon:first-child:last-child,html.theme--documenter-dark .content kbd:not(body) .icon:first-child:last-child,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--documenter-dark .tag.is-delete:not(body),html.theme--documenter-dark .content kbd.is-delete:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--documenter-dark .tag.is-delete:not(body):hover,html.theme--documenter-dark .content kbd.is-delete:not(body):hover,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--documenter-dark .tag.is-delete:not(body):focus,html.theme--documenter-dark .content kbd.is-delete:not(body):focus,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1d2122}html.theme--documenter-dark .tag.is-delete:not(body):active,html.theme--documenter-dark .content kbd.is-delete:not(body):active,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#111414}html.theme--documenter-dark .tag.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--documenter-dark .content kbd.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--documenter-dark details.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--documenter-dark a.tag:hover,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--documenter-dark .title,html.theme--documenter-dark .subtitle{word-break:break-word}html.theme--documenter-dark .title em,html.theme--documenter-dark .title span,html.theme--documenter-dark .subtitle em,html.theme--documenter-dark .subtitle span{font-weight:inherit}html.theme--documenter-dark .title sub,html.theme--documenter-dark .subtitle sub{font-size:.75em}html.theme--documenter-dark .title sup,html.theme--documenter-dark .subtitle sup{font-size:.75em}html.theme--documenter-dark .title .tag,html.theme--documenter-dark .title .content kbd,html.theme--documenter-dark .content .title kbd,html.theme--documenter-dark .title details.docstring>section>a.docs-sourcelink,html.theme--documenter-dark .subtitle .tag,html.theme--documenter-dark .subtitle .content kbd,html.theme--documenter-dark .content .subtitle kbd,html.theme--documenter-dark .subtitle details.docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--documenter-dark .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--documenter-dark .title strong{color:inherit;font-weight:inherit}html.theme--documenter-dark .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--documenter-dark .title.is-1{font-size:3rem}html.theme--documenter-dark .title.is-2{font-size:2.5rem}html.theme--documenter-dark .title.is-3{font-size:2rem}html.theme--documenter-dark .title.is-4{font-size:1.5rem}html.theme--documenter-dark .title.is-5{font-size:1.25rem}html.theme--documenter-dark .title.is-6{font-size:1rem}html.theme--documenter-dark .title.is-7{font-size:.75rem}html.theme--documenter-dark .subtitle{color:#8c9b9d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--documenter-dark .subtitle strong{color:#8c9b9d;font-weight:600}html.theme--documenter-dark .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--documenter-dark .subtitle.is-1{font-size:3rem}html.theme--documenter-dark .subtitle.is-2{font-size:2.5rem}html.theme--documenter-dark .subtitle.is-3{font-size:2rem}html.theme--documenter-dark .subtitle.is-4{font-size:1.5rem}html.theme--documenter-dark .subtitle.is-5{font-size:1.25rem}html.theme--documenter-dark .subtitle.is-6{font-size:1rem}html.theme--documenter-dark .subtitle.is-7{font-size:.75rem}html.theme--documenter-dark .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--documenter-dark .number{align-items:center;background-color:#282f2f;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#1f2424;border-color:#5e6d6f;border-radius:.4em;color:#dbdee0}html.theme--documenter-dark .select select::-moz-placeholder,html.theme--documenter-dark .textarea::-moz-placeholder,html.theme--documenter-dark .input::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select::-webkit-input-placeholder,html.theme--documenter-dark .textarea::-webkit-input-placeholder,html.theme--documenter-dark .input::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:-moz-placeholder,html.theme--documenter-dark .textarea:-moz-placeholder,html.theme--documenter-dark .input:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select:-ms-input-placeholder,html.theme--documenter-dark .textarea:-ms-input-placeholder,html.theme--documenter-dark .input:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:hover,html.theme--documenter-dark .textarea:hover,html.theme--documenter-dark .input:hover,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:hover,html.theme--documenter-dark .select select.is-hovered,html.theme--documenter-dark .is-hovered.textarea,html.theme--documenter-dark .is-hovered.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#8c9b9d}html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1abc9c;box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#8c9b9d;border-color:#282f2f;box-shadow:none;color:#fff}html.theme--documenter-dark .select select[disabled]::-moz-placeholder,html.theme--documenter-dark .textarea[disabled]::-moz-placeholder,html.theme--documenter-dark .input[disabled]::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .textarea[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .input[disabled]::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-moz-placeholder,html.theme--documenter-dark .textarea[disabled]:-moz-placeholder,html.theme--documenter-dark .input[disabled]:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-ms-input-placeholder,html.theme--documenter-dark .textarea[disabled]:-ms-input-placeholder,html.theme--documenter-dark .input[disabled]:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--documenter-dark .textarea[readonly],html.theme--documenter-dark .input[readonly],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--documenter-dark .is-white.textarea,html.theme--documenter-dark .is-white.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--documenter-dark .is-white.textarea:focus,html.theme--documenter-dark .is-white.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--documenter-dark .is-white.is-focused.textarea,html.theme--documenter-dark .is-white.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-white.textarea:active,html.theme--documenter-dark .is-white.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--documenter-dark .is-white.is-active.textarea,html.theme--documenter-dark .is-white.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .is-black.textarea,html.theme--documenter-dark .is-black.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--documenter-dark .is-black.textarea:focus,html.theme--documenter-dark .is-black.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--documenter-dark .is-black.is-focused.textarea,html.theme--documenter-dark .is-black.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-black.textarea:active,html.theme--documenter-dark .is-black.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--documenter-dark .is-black.is-active.textarea,html.theme--documenter-dark .is-black.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .is-light.textarea,html.theme--documenter-dark .is-light.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#ecf0f1}html.theme--documenter-dark .is-light.textarea:focus,html.theme--documenter-dark .is-light.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--documenter-dark .is-light.is-focused.textarea,html.theme--documenter-dark .is-light.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-light.textarea:active,html.theme--documenter-dark .is-light.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--documenter-dark .is-light.is-active.textarea,html.theme--documenter-dark .is-light.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .is-dark.textarea,html.theme--documenter-dark .content kbd.textarea,html.theme--documenter-dark .is-dark.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--documenter-dark .content kbd.input{border-color:#282f2f}html.theme--documenter-dark .is-dark.textarea:focus,html.theme--documenter-dark .content kbd.textarea:focus,html.theme--documenter-dark .is-dark.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--documenter-dark .content kbd.input:focus,html.theme--documenter-dark .is-dark.is-focused.textarea,html.theme--documenter-dark .content kbd.is-focused.textarea,html.theme--documenter-dark .is-dark.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .content kbd.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--documenter-dark .is-dark.textarea:active,html.theme--documenter-dark .content kbd.textarea:active,html.theme--documenter-dark .is-dark.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--documenter-dark .content kbd.input:active,html.theme--documenter-dark .is-dark.is-active.textarea,html.theme--documenter-dark .content kbd.is-active.textarea,html.theme--documenter-dark .is-dark.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .content kbd.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .is-primary.textarea,html.theme--documenter-dark details.docstring>section>a.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--documenter-dark details.docstring>section>a.input.docs-sourcelink{border-color:#375a7f}html.theme--documenter-dark .is-primary.textarea:focus,html.theme--documenter-dark details.docstring>section>a.textarea.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--documenter-dark details.docstring>section>a.input.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.is-focused.textarea,html.theme--documenter-dark details.docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark details.docstring>section>a.is-focused.input.docs-sourcelink,html.theme--documenter-dark .is-primary.textarea:active,html.theme--documenter-dark details.docstring>section>a.textarea.docs-sourcelink:active,html.theme--documenter-dark .is-primary.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--documenter-dark details.docstring>section>a.input.docs-sourcelink:active,html.theme--documenter-dark .is-primary.is-active.textarea,html.theme--documenter-dark details.docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark details.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .is-link.textarea,html.theme--documenter-dark .is-link.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1abc9c}html.theme--documenter-dark .is-link.textarea:focus,html.theme--documenter-dark .is-link.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--documenter-dark .is-link.is-focused.textarea,html.theme--documenter-dark .is-link.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-link.textarea:active,html.theme--documenter-dark .is-link.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--documenter-dark .is-link.is-active.textarea,html.theme--documenter-dark .is-link.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .is-info.textarea,html.theme--documenter-dark .is-info.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#3c5dcd}html.theme--documenter-dark .is-info.textarea:focus,html.theme--documenter-dark .is-info.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--documenter-dark .is-info.is-focused.textarea,html.theme--documenter-dark .is-info.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-info.textarea:active,html.theme--documenter-dark .is-info.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--documenter-dark .is-info.is-active.textarea,html.theme--documenter-dark .is-info.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .is-success.textarea,html.theme--documenter-dark .is-success.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#259a12}html.theme--documenter-dark .is-success.textarea:focus,html.theme--documenter-dark .is-success.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--documenter-dark .is-success.is-focused.textarea,html.theme--documenter-dark .is-success.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-success.textarea:active,html.theme--documenter-dark .is-success.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--documenter-dark .is-success.is-active.textarea,html.theme--documenter-dark .is-success.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .is-warning.textarea,html.theme--documenter-dark .is-warning.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#f4c72f}html.theme--documenter-dark .is-warning.textarea:focus,html.theme--documenter-dark .is-warning.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--documenter-dark .is-warning.is-focused.textarea,html.theme--documenter-dark .is-warning.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-warning.textarea:active,html.theme--documenter-dark .is-warning.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--documenter-dark .is-warning.is-active.textarea,html.theme--documenter-dark .is-warning.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .is-danger.textarea,html.theme--documenter-dark .is-danger.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#cb3c33}html.theme--documenter-dark .is-danger.textarea:focus,html.theme--documenter-dark .is-danger.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--documenter-dark .is-danger.is-focused.textarea,html.theme--documenter-dark .is-danger.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-danger.textarea:active,html.theme--documenter-dark .is-danger.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--documenter-dark .is-danger.is-active.textarea,html.theme--documenter-dark .is-danger.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .is-small.textarea,html.theme--documenter-dark .is-small.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .is-medium.textarea,html.theme--documenter-dark .is-medium.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--documenter-dark .is-large.textarea,html.theme--documenter-dark .is-large.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--documenter-dark .is-fullwidth.textarea,html.theme--documenter-dark .is-fullwidth.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--documenter-dark .is-inline.textarea,html.theme--documenter-dark .is-inline.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--documenter-dark .input.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--documenter-dark .input.is-static,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--documenter-dark .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--documenter-dark .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--documenter-dark .textarea[rows]{height:initial}html.theme--documenter-dark .textarea.has-fixed-size{resize:none}html.theme--documenter-dark .radio,html.theme--documenter-dark .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--documenter-dark .radio input,html.theme--documenter-dark .checkbox input{cursor:pointer}html.theme--documenter-dark .radio:hover,html.theme--documenter-dark .checkbox:hover{color:#8c9b9d}html.theme--documenter-dark .radio[disabled],html.theme--documenter-dark .checkbox[disabled],fieldset[disabled] html.theme--documenter-dark .radio,fieldset[disabled] html.theme--documenter-dark .checkbox,html.theme--documenter-dark .radio input[disabled],html.theme--documenter-dark .checkbox input[disabled]{color:#fff;cursor:not-allowed}html.theme--documenter-dark .radio+.radio{margin-left:.5em}html.theme--documenter-dark .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--documenter-dark .select:not(.is-multiple){height:2.5em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border-color:#1abc9c;right:1.125em;z-index:4}html.theme--documenter-dark .select.is-rounded select,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--documenter-dark .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--documenter-dark .select select::-ms-expand{display:none}html.theme--documenter-dark .select select[disabled]:hover,fieldset[disabled] html.theme--documenter-dark .select select:hover{border-color:#282f2f}html.theme--documenter-dark .select select:not([multiple]){padding-right:2.5em}html.theme--documenter-dark .select select[multiple]{height:auto;padding:0}html.theme--documenter-dark .select select[multiple] option{padding:0.5em 1em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#8c9b9d}html.theme--documenter-dark .select.is-white:not(:hover)::after{border-color:#fff}html.theme--documenter-dark .select.is-white select{border-color:#fff}html.theme--documenter-dark .select.is-white select:hover,html.theme--documenter-dark .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--documenter-dark .select.is-white select:focus,html.theme--documenter-dark .select.is-white select.is-focused,html.theme--documenter-dark .select.is-white select:active,html.theme--documenter-dark .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select:hover,html.theme--documenter-dark .select.is-black select.is-hovered{border-color:#000}html.theme--documenter-dark .select.is-black select:focus,html.theme--documenter-dark .select.is-black select.is-focused,html.theme--documenter-dark .select.is-black select:active,html.theme--documenter-dark .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .select.is-light:not(:hover)::after{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select:hover,html.theme--documenter-dark .select.is-light select.is-hovered{border-color:#dde4e6}html.theme--documenter-dark .select.is-light select:focus,html.theme--documenter-dark .select.is-light select.is-focused,html.theme--documenter-dark .select.is-light select:active,html.theme--documenter-dark .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .select.is-dark:not(:hover)::after,html.theme--documenter-dark .content kbd.select:not(:hover)::after{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select,html.theme--documenter-dark .content kbd.select select{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select:hover,html.theme--documenter-dark .content kbd.select select:hover,html.theme--documenter-dark .select.is-dark select.is-hovered,html.theme--documenter-dark .content kbd.select select.is-hovered{border-color:#1d2122}html.theme--documenter-dark .select.is-dark select:focus,html.theme--documenter-dark .content kbd.select select:focus,html.theme--documenter-dark .select.is-dark select.is-focused,html.theme--documenter-dark .content kbd.select select.is-focused,html.theme--documenter-dark .select.is-dark select:active,html.theme--documenter-dark .content kbd.select select:active,html.theme--documenter-dark .select.is-dark select.is-active,html.theme--documenter-dark .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .select.is-primary:not(:hover)::after,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select:hover,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select:hover,html.theme--documenter-dark .select.is-primary select.is-hovered,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#2f4d6d}html.theme--documenter-dark .select.is-primary select:focus,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select:focus,html.theme--documenter-dark .select.is-primary select.is-focused,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--documenter-dark .select.is-primary select:active,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select:active,html.theme--documenter-dark .select.is-primary select.is-active,html.theme--documenter-dark details.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .select.is-link:not(:hover)::after{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select:hover,html.theme--documenter-dark .select.is-link select.is-hovered{border-color:#17a689}html.theme--documenter-dark .select.is-link select:focus,html.theme--documenter-dark .select.is-link select.is-focused,html.theme--documenter-dark .select.is-link select:active,html.theme--documenter-dark .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select.is-info:not(:hover)::after{border-color:#3c5dcd}html.theme--documenter-dark .select.is-info select{border-color:#3c5dcd}html.theme--documenter-dark .select.is-info select:hover,html.theme--documenter-dark .select.is-info select.is-hovered{border-color:#3151bf}html.theme--documenter-dark .select.is-info select:focus,html.theme--documenter-dark .select.is-info select.is-focused,html.theme--documenter-dark .select.is-info select:active,html.theme--documenter-dark .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .select.is-success:not(:hover)::after{border-color:#259a12}html.theme--documenter-dark .select.is-success select{border-color:#259a12}html.theme--documenter-dark .select.is-success select:hover,html.theme--documenter-dark .select.is-success select.is-hovered{border-color:#20830f}html.theme--documenter-dark .select.is-success select:focus,html.theme--documenter-dark .select.is-success select.is-focused,html.theme--documenter-dark .select.is-success select:active,html.theme--documenter-dark .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .select.is-warning:not(:hover)::after{border-color:#f4c72f}html.theme--documenter-dark .select.is-warning select{border-color:#f4c72f}html.theme--documenter-dark .select.is-warning select:hover,html.theme--documenter-dark .select.is-warning select.is-hovered{border-color:#f3c017}html.theme--documenter-dark .select.is-warning select:focus,html.theme--documenter-dark .select.is-warning select.is-focused,html.theme--documenter-dark .select.is-warning select:active,html.theme--documenter-dark .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .select.is-danger:not(:hover)::after{border-color:#cb3c33}html.theme--documenter-dark .select.is-danger select{border-color:#cb3c33}html.theme--documenter-dark .select.is-danger select:hover,html.theme--documenter-dark .select.is-danger select.is-hovered{border-color:#b7362e}html.theme--documenter-dark .select.is-danger select:focus,html.theme--documenter-dark .select.is-danger select.is-focused,html.theme--documenter-dark .select.is-danger select:active,html.theme--documenter-dark .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .select.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .select.is-medium{font-size:1.25rem}html.theme--documenter-dark .select.is-large{font-size:1.5rem}html.theme--documenter-dark .select.is-disabled::after{border-color:#fff !important;opacity:0.5}html.theme--documenter-dark .select.is-fullwidth{width:100%}html.theme--documenter-dark .select.is-fullwidth select{width:100%}html.theme--documenter-dark .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--documenter-dark .select.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .select.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--documenter-dark .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:hover .file-cta,html.theme--documenter-dark .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:focus .file-cta,html.theme--documenter-dark .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--documenter-dark .file.is-white:active .file-cta,html.theme--documenter-dark .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:hover .file-cta,html.theme--documenter-dark .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:focus .file-cta,html.theme--documenter-dark .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--documenter-dark .file.is-black:active .file-cta,html.theme--documenter-dark .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-light .file-cta{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:hover .file-cta,html.theme--documenter-dark .file.is-light.is-hovered .file-cta{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:focus .file-cta,html.theme--documenter-dark .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(236,240,241,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:active .file-cta,html.theme--documenter-dark .file.is-light.is-active .file-cta{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-dark .file-cta,html.theme--documenter-dark .content kbd.file .file-cta{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:hover .file-cta,html.theme--documenter-dark .content kbd.file:hover .file-cta,html.theme--documenter-dark .file.is-dark.is-hovered .file-cta,html.theme--documenter-dark .content kbd.file.is-hovered .file-cta{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:focus .file-cta,html.theme--documenter-dark .content kbd.file:focus .file-cta,html.theme--documenter-dark .file.is-dark.is-focused .file-cta,html.theme--documenter-dark .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(40,47,47,0.25);color:#fff}html.theme--documenter-dark .file.is-dark:active .file-cta,html.theme--documenter-dark .content kbd.file:active .file-cta,html.theme--documenter-dark .file.is-dark.is-active .file-cta,html.theme--documenter-dark .content kbd.file.is-active .file-cta{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary .file-cta,html.theme--documenter-dark details.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:hover .file-cta,html.theme--documenter-dark details.docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--documenter-dark .file.is-primary.is-hovered .file-cta,html.theme--documenter-dark details.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:focus .file-cta,html.theme--documenter-dark details.docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--documenter-dark .file.is-primary.is-focused .file-cta,html.theme--documenter-dark details.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(55,90,127,0.25);color:#fff}html.theme--documenter-dark .file.is-primary:active .file-cta,html.theme--documenter-dark details.docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--documenter-dark .file.is-primary.is-active .file-cta,html.theme--documenter-dark details.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link .file-cta{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:hover .file-cta,html.theme--documenter-dark .file.is-link.is-hovered .file-cta{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:focus .file-cta,html.theme--documenter-dark .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(26,188,156,0.25);color:#fff}html.theme--documenter-dark .file.is-link:active .file-cta,html.theme--documenter-dark .file.is-link.is-active .file-cta{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info .file-cta{background-color:#3c5dcd;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:hover .file-cta,html.theme--documenter-dark .file.is-info.is-hovered .file-cta{background-color:#3355c9;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:focus .file-cta,html.theme--documenter-dark .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(60,93,205,0.25);color:#fff}html.theme--documenter-dark .file.is-info:active .file-cta,html.theme--documenter-dark .file.is-info.is-active .file-cta{background-color:#3151bf;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success .file-cta{background-color:#259a12;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:hover .file-cta,html.theme--documenter-dark .file.is-success.is-hovered .file-cta{background-color:#228f11;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:focus .file-cta,html.theme--documenter-dark .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(37,154,18,0.25);color:#fff}html.theme--documenter-dark .file.is-success:active .file-cta,html.theme--documenter-dark .file.is-success.is-active .file-cta{background-color:#20830f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning .file-cta{background-color:#f4c72f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:hover .file-cta,html.theme--documenter-dark .file.is-warning.is-hovered .file-cta{background-color:#f3c423;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:focus .file-cta,html.theme--documenter-dark .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(244,199,47,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:active .file-cta,html.theme--documenter-dark .file.is-warning.is-active .file-cta{background-color:#f3c017;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-danger .file-cta{background-color:#cb3c33;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:hover .file-cta,html.theme--documenter-dark .file.is-danger.is-hovered .file-cta{background-color:#c13930;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:focus .file-cta,html.theme--documenter-dark .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(203,60,51,0.25);color:#fff}html.theme--documenter-dark .file.is-danger:active .file-cta,html.theme--documenter-dark .file.is-danger.is-active .file-cta{background-color:#b7362e;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--documenter-dark .file.is-normal{font-size:1rem}html.theme--documenter-dark .file.is-medium{font-size:1.25rem}html.theme--documenter-dark .file.is-medium .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-large{font-size:1.5rem}html.theme--documenter-dark .file.is-large .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--documenter-dark .file.has-name.is-empty .file-name{display:none}html.theme--documenter-dark .file.is-boxed .file-label{flex-direction:column}html.theme--documenter-dark .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--documenter-dark .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--documenter-dark .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--documenter-dark .file.is-boxed .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-boxed.is-small .file-icon .fa,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--documenter-dark .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--documenter-dark .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--documenter-dark .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--documenter-dark .file.is-centered{justify-content:center}html.theme--documenter-dark .file.is-fullwidth .file-label{width:100%}html.theme--documenter-dark .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--documenter-dark .file.is-right{justify-content:flex-end}html.theme--documenter-dark .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--documenter-dark .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--documenter-dark .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--documenter-dark .file-label:hover .file-cta{background-color:#232829;color:#f2f2f2}html.theme--documenter-dark .file-label:hover .file-name{border-color:#596668}html.theme--documenter-dark .file-label:active .file-cta{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .file-label:active .file-name{border-color:#535f61}html.theme--documenter-dark .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--documenter-dark .file-cta{background-color:#282f2f;color:#fff}html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--documenter-dark .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--documenter-dark .file-icon .fa{font-size:14px}html.theme--documenter-dark .label{color:#f2f2f2;display:block;font-size:1rem;font-weight:700}html.theme--documenter-dark .label:not(:last-child){margin-bottom:0.5em}html.theme--documenter-dark .label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--documenter-dark .label.is-medium{font-size:1.25rem}html.theme--documenter-dark .label.is-large{font-size:1.5rem}html.theme--documenter-dark .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--documenter-dark .help.is-white{color:#fff}html.theme--documenter-dark .help.is-black{color:#0a0a0a}html.theme--documenter-dark .help.is-light{color:#ecf0f1}html.theme--documenter-dark .help.is-dark,html.theme--documenter-dark .content kbd.help{color:#282f2f}html.theme--documenter-dark .help.is-primary,html.theme--documenter-dark details.docstring>section>a.help.docs-sourcelink{color:#375a7f}html.theme--documenter-dark .help.is-link{color:#1abc9c}html.theme--documenter-dark .help.is-info{color:#3c5dcd}html.theme--documenter-dark .help.is-success{color:#259a12}html.theme--documenter-dark .help.is-warning{color:#f4c72f}html.theme--documenter-dark .help.is-danger{color:#cb3c33}html.theme--documenter-dark .field:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.has-addons{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--documenter-dark .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.has-addons.has-addons-centered{justify-content:center}html.theme--documenter-dark .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--documenter-dark .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .field.is-grouped{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.is-grouped>.control{flex-shrink:0}html.theme--documenter-dark .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--documenter-dark .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field.is-horizontal{display:flex}}html.theme--documenter-dark .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--documenter-dark .field-label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-normal{padding-top:0.375em}html.theme--documenter-dark .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--documenter-dark .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--documenter-dark .field-body .field{margin-bottom:0}html.theme--documenter-dark .field-body>.field{flex-shrink:1}html.theme--documenter-dark .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--documenter-dark .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--documenter-dark .control.has-icons-left .input:focus~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-left .select:focus~.icon,html.theme--documenter-dark .control.has-icons-right .input:focus~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-right .select:focus~.icon{color:#282f2f}html.theme--documenter-dark .control.has-icons-left .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-small~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--documenter-dark .control.has-icons-left .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--documenter-dark .control.has-icons-left .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon{color:#5e6d6f;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--documenter-dark .control.has-icons-left .input,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--documenter-dark .control.has-icons-left .select select{padding-left:2.5em}html.theme--documenter-dark .control.has-icons-left .icon.is-left{left:0}html.theme--documenter-dark .control.has-icons-right .input,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--documenter-dark .control.has-icons-right .select select{padding-right:2.5em}html.theme--documenter-dark .control.has-icons-right .icon.is-right{right:0}html.theme--documenter-dark .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--documenter-dark .control.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .control.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--documenter-dark .breadcrumb a{align-items:center;color:#1abc9c;display:initial;justify-content:center;padding:0 .75em}html.theme--documenter-dark .breadcrumb a:hover{color:#1dd2af}html.theme--documenter-dark .breadcrumb li{align-items:center;display:flex}html.theme--documenter-dark .breadcrumb li:first-child a{padding-left:0}html.theme--documenter-dark .breadcrumb li.is-active a{color:#f2f2f2;cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb li+li::before{color:#8c9b9d;content:"\0002f"}html.theme--documenter-dark .breadcrumb ul,html.theme--documenter-dark .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .breadcrumb .icon:first-child{margin-right:.5em}html.theme--documenter-dark .breadcrumb .icon:last-child{margin-left:.5em}html.theme--documenter-dark .breadcrumb.is-centered ol,html.theme--documenter-dark .breadcrumb.is-centered ul{justify-content:center}html.theme--documenter-dark .breadcrumb.is-right ol,html.theme--documenter-dark .breadcrumb.is-right ul{justify-content:flex-end}html.theme--documenter-dark .breadcrumb.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--documenter-dark .breadcrumb.is-medium{font-size:1.25rem}html.theme--documenter-dark .breadcrumb.is-large{font-size:1.5rem}html.theme--documenter-dark .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--documenter-dark .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--documenter-dark .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--documenter-dark .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--documenter-dark .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#fff;max-width:100%;position:relative}html.theme--documenter-dark .card-footer:first-child,html.theme--documenter-dark .card-content:first-child,html.theme--documenter-dark .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-footer:last-child,html.theme--documenter-dark .card-content:last-child,html.theme--documenter-dark .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--documenter-dark .card-header-title{align-items:center;color:#f2f2f2;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--documenter-dark .card-header-title.is-centered{justify-content:center}html.theme--documenter-dark .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--documenter-dark .card-image{display:block;position:relative}html.theme--documenter-dark .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--documenter-dark .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--documenter-dark .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--documenter-dark .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--documenter-dark .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--documenter-dark .dropdown.is-active .dropdown-menu,html.theme--documenter-dark .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--documenter-dark .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--documenter-dark .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--documenter-dark .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .dropdown-content{background-color:#282f2f;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--documenter-dark .dropdown-item{color:#fff;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--documenter-dark a.dropdown-item,html.theme--documenter-dark button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--documenter-dark a.dropdown-item:hover,html.theme--documenter-dark button.dropdown-item:hover{background-color:#282f2f;color:#0a0a0a}html.theme--documenter-dark a.dropdown-item.is-active,html.theme--documenter-dark button.dropdown-item.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--documenter-dark .level{align-items:center;justify-content:space-between}html.theme--documenter-dark .level code{border-radius:.4em}html.theme--documenter-dark .level img{display:inline-block;vertical-align:top}html.theme--documenter-dark .level.is-mobile{display:flex}html.theme--documenter-dark .level.is-mobile .level-left,html.theme--documenter-dark .level.is-mobile .level-right{display:flex}html.theme--documenter-dark .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--documenter-dark .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level{display:flex}html.theme--documenter-dark .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--documenter-dark .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--documenter-dark .level-item .title,html.theme--documenter-dark .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--documenter-dark .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--documenter-dark .level-left,html.theme--documenter-dark .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .level-left .level-item.is-flexible,html.theme--documenter-dark .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left .level-item:not(:last-child),html.theme--documenter-dark .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--documenter-dark .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left{display:flex}}html.theme--documenter-dark .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-right{display:flex}}html.theme--documenter-dark .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--documenter-dark .media .content:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .media .media{border-top:1px solid rgba(94,109,111,0.5);display:flex;padding-top:.75rem}html.theme--documenter-dark .media .media .content:not(:last-child),html.theme--documenter-dark .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--documenter-dark .media .media .media{padding-top:.5rem}html.theme--documenter-dark .media .media .media+.media{margin-top:.5rem}html.theme--documenter-dark .media+.media{border-top:1px solid rgba(94,109,111,0.5);margin-top:1rem;padding-top:1rem}html.theme--documenter-dark .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--documenter-dark .media-left,html.theme--documenter-dark .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .media-left{margin-right:1rem}html.theme--documenter-dark .media-right{margin-left:1rem}html.theme--documenter-dark .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .media-content{overflow-x:auto}}html.theme--documenter-dark .menu{font-size:1rem}html.theme--documenter-dark .menu.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--documenter-dark .menu.is-medium{font-size:1.25rem}html.theme--documenter-dark .menu.is-large{font-size:1.5rem}html.theme--documenter-dark .menu-list{line-height:1.25}html.theme--documenter-dark .menu-list a{border-radius:3px;color:#fff;display:block;padding:0.5em 0.75em}html.theme--documenter-dark .menu-list a:hover{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .menu-list a.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .menu-list li ul{border-left:1px solid #5e6d6f;margin:.75em;padding-left:.75em}html.theme--documenter-dark .menu-label{color:#fff;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--documenter-dark .menu-label:not(:first-child){margin-top:1em}html.theme--documenter-dark .menu-label:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .message{background-color:#282f2f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .message strong{color:currentColor}html.theme--documenter-dark .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .message.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--documenter-dark .message.is-medium{font-size:1.25rem}html.theme--documenter-dark .message.is-large{font-size:1.5rem}html.theme--documenter-dark .message.is-white{background-color:#fff}html.theme--documenter-dark .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .message.is-white .message-body{border-color:#fff}html.theme--documenter-dark .message.is-black{background-color:#fafafa}html.theme--documenter-dark .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .message.is-black .message-body{border-color:#0a0a0a}html.theme--documenter-dark .message.is-light{background-color:#f9fafb}html.theme--documenter-dark .message.is-light .message-header{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-light .message-body{border-color:#ecf0f1}html.theme--documenter-dark .message.is-dark,html.theme--documenter-dark .content kbd.message{background-color:#f9fafa}html.theme--documenter-dark .message.is-dark .message-header,html.theme--documenter-dark .content kbd.message .message-header{background-color:#282f2f;color:#fff}html.theme--documenter-dark .message.is-dark .message-body,html.theme--documenter-dark .content kbd.message .message-body{border-color:#282f2f}html.theme--documenter-dark .message.is-primary,html.theme--documenter-dark details.docstring>section>a.message.docs-sourcelink{background-color:#f1f5f9}html.theme--documenter-dark .message.is-primary .message-header,html.theme--documenter-dark details.docstring>section>a.message.docs-sourcelink .message-header{background-color:#375a7f;color:#fff}html.theme--documenter-dark .message.is-primary .message-body,html.theme--documenter-dark details.docstring>section>a.message.docs-sourcelink .message-body{border-color:#375a7f;color:#4d7eb2}html.theme--documenter-dark .message.is-link{background-color:#edfdf9}html.theme--documenter-dark .message.is-link .message-header{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .message.is-link .message-body{border-color:#1abc9c;color:#15987e}html.theme--documenter-dark .message.is-info{background-color:#eff2fb}html.theme--documenter-dark .message.is-info .message-header{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .message.is-info .message-body{border-color:#3c5dcd;color:#3253c3}html.theme--documenter-dark .message.is-success{background-color:#effded}html.theme--documenter-dark .message.is-success .message-header{background-color:#259a12;color:#fff}html.theme--documenter-dark .message.is-success .message-body{border-color:#259a12;color:#2ec016}html.theme--documenter-dark .message.is-warning{background-color:#fefaec}html.theme--documenter-dark .message.is-warning .message-header{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-warning .message-body{border-color:#f4c72f;color:#8c6e07}html.theme--documenter-dark .message.is-danger{background-color:#fbefef}html.theme--documenter-dark .message.is-danger .message-header{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .message.is-danger .message-body{border-color:#cb3c33;color:#c03930}html.theme--documenter-dark .message-header{align-items:center;background-color:#fff;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--documenter-dark .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--documenter-dark .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--documenter-dark .message-body{border-color:#5e6d6f;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#fff;padding:1.25em 1.5em}html.theme--documenter-dark .message-body code,html.theme--documenter-dark .message-body pre{background-color:#fff}html.theme--documenter-dark .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--documenter-dark .modal.is-active{display:flex}html.theme--documenter-dark .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--documenter-dark .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--documenter-dark .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--documenter-dark .modal-card-head,html.theme--documenter-dark .modal-card-foot{align-items:center;background-color:#282f2f;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--documenter-dark .modal-card-head{border-bottom:1px solid #5e6d6f;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--documenter-dark .modal-card-title{color:#f2f2f2;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--documenter-dark .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5e6d6f}html.theme--documenter-dark .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--documenter-dark .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--documenter-dark .navbar{background-color:#375a7f;min-height:4rem;position:relative;z-index:30}html.theme--documenter-dark .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-white .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--documenter-dark .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-black .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--documenter-dark .navbar.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-light .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-dark,html.theme--documenter-dark .content kbd.navbar{background-color:#282f2f;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-burger,html.theme--documenter-dark .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-dark .navbar-start>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-end>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#282f2f;color:#fff}}html.theme--documenter-dark .navbar.is-primary,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>.navbar-item,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-burger,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-primary .navbar-start>.navbar-item,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-end>.navbar-item,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link::after,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link::after,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#375a7f;color:#fff}}html.theme--documenter-dark .navbar.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-link .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c;color:#fff}}html.theme--documenter-dark .navbar.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-info .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3c5dcd;color:#fff}}html.theme--documenter-dark .navbar.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-success .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#259a12;color:#fff}}html.theme--documenter-dark .navbar.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-warning .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#f4c72f;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-danger .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#cb3c33;color:#fff}}html.theme--documenter-dark .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--documenter-dark .navbar.has-shadow{box-shadow:0 2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-bottom,html.theme--documenter-dark .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-top{top:0}html.theme--documenter-dark html.has-navbar-fixed-top,html.theme--documenter-dark body.has-navbar-fixed-top{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom,html.theme--documenter-dark body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--documenter-dark .navbar-brand,html.theme--documenter-dark .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--documenter-dark .navbar-brand a.navbar-item:focus,html.theme--documenter-dark .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--documenter-dark .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--documenter-dark .navbar-burger{color:#fff;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--documenter-dark .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--documenter-dark .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--documenter-dark .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--documenter-dark .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--documenter-dark .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--documenter-dark .navbar-menu{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{color:#fff;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--documenter-dark .navbar-item .icon:only-child,html.theme--documenter-dark .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--documenter-dark a.navbar-item,html.theme--documenter-dark .navbar-link{cursor:pointer}html.theme--documenter-dark a.navbar-item:focus,html.theme--documenter-dark a.navbar-item:focus-within,html.theme--documenter-dark a.navbar-item:hover,html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link:focus,html.theme--documenter-dark .navbar-link:focus-within,html.theme--documenter-dark .navbar-link:hover,html.theme--documenter-dark .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-item{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .navbar-item img{max-height:1.75rem}html.theme--documenter-dark .navbar-item.has-dropdown{padding:0}html.theme--documenter-dark .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--documenter-dark .navbar-item.is-tab:focus,html.theme--documenter-dark .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c}html.theme--documenter-dark .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c;border-bottom-style:solid;border-bottom-width:3px;color:#1abc9c;padding-bottom:calc(0.5rem - 3px)}html.theme--documenter-dark .navbar-content{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--documenter-dark .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--documenter-dark .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar>.container{display:block}html.theme--documenter-dark .navbar-brand .navbar-item,html.theme--documenter-dark .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--documenter-dark .navbar-link::after{display:none}html.theme--documenter-dark .navbar-menu{background-color:#375a7f;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--documenter-dark .navbar-menu.is-active{display:block}html.theme--documenter-dark .navbar.is-fixed-bottom-touch,html.theme--documenter-dark .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-touch{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-touch{top:0}html.theme--documenter-dark .navbar.is-fixed-top .navbar-menu,html.theme--documenter-dark .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--documenter-dark html.has-navbar-fixed-top-touch,html.theme--documenter-dark body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-touch,html.theme--documenter-dark body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar,html.theme--documenter-dark .navbar-menu,html.theme--documenter-dark .navbar-start,html.theme--documenter-dark .navbar-end{align-items:stretch;display:flex}html.theme--documenter-dark .navbar{min-height:4rem}html.theme--documenter-dark .navbar.is-spaced{padding:1rem 2rem}html.theme--documenter-dark .navbar.is-spaced .navbar-start,html.theme--documenter-dark .navbar.is-spaced .navbar-end{align-items:center}html.theme--documenter-dark .navbar.is-spaced a.navbar-item,html.theme--documenter-dark .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent a.navbar-item:hover,html.theme--documenter-dark .navbar.is-transparent a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-transparent .navbar-link:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-link:hover,html.theme--documenter-dark .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-burger{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{align-items:center;display:flex}html.theme--documenter-dark .navbar-item.has-dropdown{align-items:stretch}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--documenter-dark .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--documenter-dark .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--documenter-dark .navbar-dropdown{background-color:#375a7f;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--documenter-dark .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--documenter-dark .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}.navbar.is-spaced html.theme--documenter-dark .navbar-dropdown,html.theme--documenter-dark .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--documenter-dark .navbar-dropdown.is-right{left:auto;right:0}html.theme--documenter-dark .navbar-divider{display:block}html.theme--documenter-dark .navbar>.container .navbar-brand,html.theme--documenter-dark .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--documenter-dark .navbar>.container .navbar-menu,html.theme--documenter-dark .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop,html.theme--documenter-dark .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-desktop{top:0}html.theme--documenter-dark html.has-navbar-fixed-top-desktop,html.theme--documenter-dark body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-desktop,html.theme--documenter-dark body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-top,html.theme--documenter-dark body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-bottom,html.theme--documenter-dark body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link.is-active{color:#1abc9c}html.theme--documenter-dark a.navbar-item.is-active:not(:focus):not(:hover),html.theme--documenter-dark .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--documenter-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--documenter-dark .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--documenter-dark .pagination{font-size:1rem;margin:-.25rem}html.theme--documenter-dark .pagination.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--documenter-dark .pagination.is-medium{font-size:1.25rem}html.theme--documenter-dark .pagination.is-large{font-size:1.5rem}html.theme--documenter-dark .pagination.is-rounded .pagination-previous,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--documenter-dark .pagination.is-rounded .pagination-next,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--documenter-dark .pagination.is-rounded .pagination-link,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--documenter-dark .pagination,html.theme--documenter-dark .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link{border-color:#5e6d6f;color:#1abc9c;min-width:2.5em}html.theme--documenter-dark .pagination-previous:hover,html.theme--documenter-dark .pagination-next:hover,html.theme--documenter-dark .pagination-link:hover{border-color:#8c9b9d;color:#1dd2af}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus{border-color:#8c9b9d}html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-previous.is-disabled,html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-next.is-disabled,html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-link.is-disabled{background-color:#5e6d6f;border-color:#5e6d6f;box-shadow:none;color:#fff;opacity:0.5}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--documenter-dark .pagination-link.is-current{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .pagination-ellipsis{color:#8c9b9d;pointer-events:none}html.theme--documenter-dark .pagination-list{flex-wrap:wrap}html.theme--documenter-dark .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--documenter-dark .pagination{flex-wrap:wrap}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination-previous{order:2}html.theme--documenter-dark .pagination-next{order:3}html.theme--documenter-dark .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination.is-centered .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--documenter-dark .pagination.is-centered .pagination-next{order:3}html.theme--documenter-dark .pagination.is-right .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-right .pagination-next{order:2}html.theme--documenter-dark .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--documenter-dark .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--documenter-dark .panel:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--documenter-dark .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--documenter-dark .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--documenter-dark .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--documenter-dark .panel.is-light .panel-heading{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-light .panel-tabs a.is-active{border-bottom-color:#ecf0f1}html.theme--documenter-dark .panel.is-light .panel-block.is-active .panel-icon{color:#ecf0f1}html.theme--documenter-dark .panel.is-dark .panel-heading,html.theme--documenter-dark .content kbd.panel .panel-heading{background-color:#282f2f;color:#fff}html.theme--documenter-dark .panel.is-dark .panel-tabs a.is-active,html.theme--documenter-dark .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#282f2f}html.theme--documenter-dark .panel.is-dark .panel-block.is-active .panel-icon,html.theme--documenter-dark .content kbd.panel .panel-block.is-active .panel-icon{color:#282f2f}html.theme--documenter-dark .panel.is-primary .panel-heading,html.theme--documenter-dark details.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#375a7f;color:#fff}html.theme--documenter-dark .panel.is-primary .panel-tabs a.is-active,html.theme--documenter-dark details.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#375a7f}html.theme--documenter-dark .panel.is-primary .panel-block.is-active .panel-icon,html.theme--documenter-dark details.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#375a7f}html.theme--documenter-dark .panel.is-link .panel-heading{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1abc9c}html.theme--documenter-dark .panel.is-link .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel.is-info .panel-heading{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .panel.is-info .panel-tabs a.is-active{border-bottom-color:#3c5dcd}html.theme--documenter-dark .panel.is-info .panel-block.is-active .panel-icon{color:#3c5dcd}html.theme--documenter-dark .panel.is-success .panel-heading{background-color:#259a12;color:#fff}html.theme--documenter-dark .panel.is-success .panel-tabs a.is-active{border-bottom-color:#259a12}html.theme--documenter-dark .panel.is-success .panel-block.is-active .panel-icon{color:#259a12}html.theme--documenter-dark .panel.is-warning .panel-heading{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#f4c72f}html.theme--documenter-dark .panel.is-warning .panel-block.is-active .panel-icon{color:#f4c72f}html.theme--documenter-dark .panel.is-danger .panel-heading{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#cb3c33}html.theme--documenter-dark .panel.is-danger .panel-block.is-active .panel-icon{color:#cb3c33}html.theme--documenter-dark .panel-tabs:not(:last-child),html.theme--documenter-dark .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--documenter-dark .panel-heading{background-color:#343c3d;border-radius:8px 8px 0 0;color:#f2f2f2;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--documenter-dark .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--documenter-dark .panel-tabs a{border-bottom:1px solid #5e6d6f;margin-bottom:-1px;padding:0.5em}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#343c3d;color:#17a689}html.theme--documenter-dark .panel-list a{color:#fff}html.theme--documenter-dark .panel-list a:hover{color:#1abc9c}html.theme--documenter-dark .panel-block{align-items:center;color:#f2f2f2;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--documenter-dark .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--documenter-dark .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--documenter-dark .panel-block.is-wrapped{flex-wrap:wrap}html.theme--documenter-dark .panel-block.is-active{border-left-color:#1abc9c;color:#17a689}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--documenter-dark a.panel-block,html.theme--documenter-dark label.panel-block{cursor:pointer}html.theme--documenter-dark a.panel-block:hover,html.theme--documenter-dark label.panel-block:hover{background-color:#282f2f}html.theme--documenter-dark .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#fff;margin-right:.75em}html.theme--documenter-dark .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--documenter-dark .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--documenter-dark .tabs a{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;color:#fff;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--documenter-dark .tabs a:hover{border-bottom-color:#f2f2f2;color:#f2f2f2}html.theme--documenter-dark .tabs li{display:block}html.theme--documenter-dark .tabs li.is-active a{border-bottom-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .tabs ul{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--documenter-dark .tabs ul.is-left{padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--documenter-dark .tabs .icon:first-child{margin-right:.5em}html.theme--documenter-dark .tabs .icon:last-child{margin-left:.5em}html.theme--documenter-dark .tabs.is-centered ul{justify-content:center}html.theme--documenter-dark .tabs.is-right ul{justify-content:flex-end}html.theme--documenter-dark .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--documenter-dark .tabs.is-boxed a:hover{background-color:#282f2f;border-bottom-color:#5e6d6f}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5e6d6f;border-bottom-color:rgba(0,0,0,0) !important}html.theme--documenter-dark .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .tabs.is-toggle a{border-color:#5e6d6f;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--documenter-dark .tabs.is-toggle a:hover{background-color:#282f2f;border-color:#8c9b9d;z-index:2}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li.is-active a{background-color:#1abc9c;border-color:#1abc9c;color:#fff;z-index:1}html.theme--documenter-dark .tabs.is-toggle ul{border-bottom:none}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--documenter-dark .tabs.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--documenter-dark .tabs.is-medium{font-size:1.25rem}html.theme--documenter-dark .tabs.is-large{font-size:1.5rem}html.theme--documenter-dark .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--documenter-dark .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--documenter-dark .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--documenter-dark .column.is-narrow-mobile{flex:none;width:unset}html.theme--documenter-dark .column.is-full-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-mobile{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--documenter-dark .column.is-0-mobile{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-mobile{margin-left:0%}html.theme--documenter-dark .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-mobile{margin-left:25%}html.theme--documenter-dark .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-mobile{margin-left:50%}html.theme--documenter-dark .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-mobile{margin-left:75%}html.theme--documenter-dark .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .column.is-narrow,html.theme--documenter-dark .column.is-narrow-tablet{flex:none;width:unset}html.theme--documenter-dark .column.is-full,html.theme--documenter-dark .column.is-full-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters,html.theme--documenter-dark .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds,html.theme--documenter-dark .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half,html.theme--documenter-dark .column.is-half-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third,html.theme--documenter-dark .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter,html.theme--documenter-dark .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth,html.theme--documenter-dark .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths,html.theme--documenter-dark .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths,html.theme--documenter-dark .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths,html.theme--documenter-dark .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters,html.theme--documenter-dark .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds,html.theme--documenter-dark .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half,html.theme--documenter-dark .column.is-offset-half-tablet{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third,html.theme--documenter-dark .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter,html.theme--documenter-dark .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth,html.theme--documenter-dark .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths,html.theme--documenter-dark .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths,html.theme--documenter-dark .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths,html.theme--documenter-dark .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--documenter-dark .column.is-0,html.theme--documenter-dark .column.is-0-tablet{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0,html.theme--documenter-dark .column.is-offset-0-tablet{margin-left:0%}html.theme--documenter-dark .column.is-1,html.theme--documenter-dark .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1,html.theme--documenter-dark .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2,html.theme--documenter-dark .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2,html.theme--documenter-dark .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3,html.theme--documenter-dark .column.is-3-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3,html.theme--documenter-dark .column.is-offset-3-tablet{margin-left:25%}html.theme--documenter-dark .column.is-4,html.theme--documenter-dark .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4,html.theme--documenter-dark .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5,html.theme--documenter-dark .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5,html.theme--documenter-dark .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6,html.theme--documenter-dark .column.is-6-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6,html.theme--documenter-dark .column.is-offset-6-tablet{margin-left:50%}html.theme--documenter-dark .column.is-7,html.theme--documenter-dark .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7,html.theme--documenter-dark .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8,html.theme--documenter-dark .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8,html.theme--documenter-dark .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9,html.theme--documenter-dark .column.is-9-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9,html.theme--documenter-dark .column.is-offset-9-tablet{margin-left:75%}html.theme--documenter-dark .column.is-10,html.theme--documenter-dark .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10,html.theme--documenter-dark .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11,html.theme--documenter-dark .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11,html.theme--documenter-dark .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12,html.theme--documenter-dark .column.is-12-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12,html.theme--documenter-dark .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--documenter-dark .column.is-narrow-touch{flex:none;width:unset}html.theme--documenter-dark .column.is-full-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-touch{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-touch{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-touch{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-touch{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-touch{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--documenter-dark .column.is-0-touch{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-touch{margin-left:0%}html.theme--documenter-dark .column.is-1-touch{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-touch{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-touch{margin-left:25%}html.theme--documenter-dark .column.is-4-touch{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-touch{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-touch{margin-left:50%}html.theme--documenter-dark .column.is-7-touch{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-touch{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-touch{margin-left:75%}html.theme--documenter-dark .column.is-10-touch{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-touch{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--documenter-dark .column.is-narrow-desktop{flex:none;width:unset}html.theme--documenter-dark .column.is-full-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-desktop{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--documenter-dark .column.is-0-desktop{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-desktop{margin-left:0%}html.theme--documenter-dark .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-desktop{margin-left:25%}html.theme--documenter-dark .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-desktop{margin-left:50%}html.theme--documenter-dark .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-desktop{margin-left:75%}html.theme--documenter-dark .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--documenter-dark .column.is-narrow-widescreen{flex:none;width:unset}html.theme--documenter-dark .column.is-full-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--documenter-dark .column.is-0-widescreen{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-widescreen{margin-left:0%}html.theme--documenter-dark .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--documenter-dark .column.is-narrow-fullhd{flex:none;width:unset}html.theme--documenter-dark .column.is-full-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--documenter-dark .column.is-0-fullhd{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-fullhd{margin-left:0%}html.theme--documenter-dark .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-fullhd{margin-left:100%}}html.theme--documenter-dark .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .columns:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--documenter-dark .columns.is-centered{justify-content:center}html.theme--documenter-dark .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--documenter-dark .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--documenter-dark .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .columns.is-gapless:last-child{margin-bottom:0}html.theme--documenter-dark .columns.is-mobile{display:flex}html.theme--documenter-dark .columns.is-multiline{flex-wrap:wrap}html.theme--documenter-dark .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-desktop{display:flex}}html.theme--documenter-dark .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--documenter-dark .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--documenter-dark .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--documenter-dark .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--documenter-dark .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--documenter-dark .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--documenter-dark .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--documenter-dark .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--documenter-dark .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--documenter-dark .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--documenter-dark .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--documenter-dark .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--documenter-dark .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .tile.is-child{margin:0 !important}html.theme--documenter-dark .tile.is-parent{padding:.75rem}html.theme--documenter-dark .tile.is-vertical{flex-direction:column}html.theme--documenter-dark .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--documenter-dark .tile:not(.is-child){display:flex}html.theme--documenter-dark .tile.is-1{flex:none;width:8.33333337%}html.theme--documenter-dark .tile.is-2{flex:none;width:16.66666674%}html.theme--documenter-dark .tile.is-3{flex:none;width:25%}html.theme--documenter-dark .tile.is-4{flex:none;width:33.33333337%}html.theme--documenter-dark .tile.is-5{flex:none;width:41.66666674%}html.theme--documenter-dark .tile.is-6{flex:none;width:50%}html.theme--documenter-dark .tile.is-7{flex:none;width:58.33333337%}html.theme--documenter-dark .tile.is-8{flex:none;width:66.66666674%}html.theme--documenter-dark .tile.is-9{flex:none;width:75%}html.theme--documenter-dark .tile.is-10{flex:none;width:83.33333337%}html.theme--documenter-dark .tile.is-11{flex:none;width:91.66666674%}html.theme--documenter-dark .tile.is-12{flex:none;width:100%}}html.theme--documenter-dark .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--documenter-dark .hero .navbar{background:none}html.theme--documenter-dark .hero .tabs ul{border-bottom:none}html.theme--documenter-dark .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-white strong{color:inherit}html.theme--documenter-dark .hero.is-white .title{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--documenter-dark .hero.is-white .subtitle a:not(.button),html.theme--documenter-dark .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-white .navbar-menu{background-color:#fff}}html.theme--documenter-dark .hero.is-white .navbar-item,html.theme--documenter-dark .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--documenter-dark .hero.is-white a.navbar-item:hover,html.theme--documenter-dark .hero.is-white a.navbar-item.is-active,html.theme--documenter-dark .hero.is-white .navbar-link:hover,html.theme--documenter-dark .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--documenter-dark .hero.is-white .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--documenter-dark .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-black strong{color:inherit}html.theme--documenter-dark .hero.is-black .title{color:#fff}html.theme--documenter-dark .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-black .subtitle a:not(.button),html.theme--documenter-dark .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--documenter-dark .hero.is-black .navbar-item,html.theme--documenter-dark .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-black a.navbar-item:hover,html.theme--documenter-dark .hero.is-black a.navbar-item.is-active,html.theme--documenter-dark .hero.is-black .navbar-link:hover,html.theme--documenter-dark .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-black .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--documenter-dark .hero.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-light strong{color:inherit}html.theme--documenter-dark .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-light .subtitle a:not(.button),html.theme--documenter-dark .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-light .navbar-menu{background-color:#ecf0f1}}html.theme--documenter-dark .hero.is-light .navbar-item,html.theme--documenter-dark .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a.navbar-item:hover,html.theme--documenter-dark .hero.is-light a.navbar-item.is-active,html.theme--documenter-dark .hero.is-light .navbar-link:hover,html.theme--documenter-dark .hero.is-light .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-light .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-light .tabs li.is-active a{color:#ecf0f1 !important;opacity:1}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .hero.is-light.is-bold{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}}html.theme--documenter-dark .hero.is-dark,html.theme--documenter-dark .content kbd.hero{background-color:#282f2f;color:#fff}html.theme--documenter-dark .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-dark strong,html.theme--documenter-dark .content kbd.hero strong{color:inherit}html.theme--documenter-dark .hero.is-dark .title,html.theme--documenter-dark .content kbd.hero .title{color:#fff}html.theme--documenter-dark .hero.is-dark .subtitle,html.theme--documenter-dark .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-dark .subtitle a:not(.button),html.theme--documenter-dark .content kbd.hero .subtitle a:not(.button),html.theme--documenter-dark .hero.is-dark .subtitle strong,html.theme--documenter-dark .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-dark .navbar-menu,html.theme--documenter-dark .content kbd.hero .navbar-menu{background-color:#282f2f}}html.theme--documenter-dark .hero.is-dark .navbar-item,html.theme--documenter-dark .content kbd.hero .navbar-item,html.theme--documenter-dark .hero.is-dark .navbar-link,html.theme--documenter-dark .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-dark a.navbar-item:hover,html.theme--documenter-dark .content kbd.hero a.navbar-item:hover,html.theme--documenter-dark .hero.is-dark a.navbar-item.is-active,html.theme--documenter-dark .content kbd.hero a.navbar-item.is-active,html.theme--documenter-dark .hero.is-dark .navbar-link:hover,html.theme--documenter-dark .content kbd.hero .navbar-link:hover,html.theme--documenter-dark .hero.is-dark .navbar-link.is-active,html.theme--documenter-dark .content kbd.hero .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .hero.is-dark .tabs a,html.theme--documenter-dark .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-dark .tabs a:hover,html.theme--documenter-dark .content kbd.hero .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-dark .tabs li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs li.is-active a{color:#282f2f !important;opacity:1}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#282f2f}html.theme--documenter-dark .hero.is-dark.is-bold,html.theme--documenter-dark .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-dark.is-bold .navbar-menu,html.theme--documenter-dark .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}}html.theme--documenter-dark .hero.is-primary,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-primary strong,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--documenter-dark .hero.is-primary .title,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--documenter-dark .hero.is-primary .subtitle,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-primary .subtitle a:not(.button),html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--documenter-dark .hero.is-primary .subtitle strong,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-primary .navbar-menu,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#375a7f}}html.theme--documenter-dark .hero.is-primary .navbar-item,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--documenter-dark .hero.is-primary .navbar-link,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-primary a.navbar-item:hover,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--documenter-dark .hero.is-primary a.navbar-item.is-active,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--documenter-dark .hero.is-primary .navbar-link:hover,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--documenter-dark .hero.is-primary .navbar-link.is-active,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .hero.is-primary .tabs a,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-primary .tabs a:hover,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-primary .tabs li.is-active a,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#375a7f !important;opacity:1}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a:hover,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a:hover,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#375a7f}html.theme--documenter-dark .hero.is-primary.is-bold,html.theme--documenter-dark details.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-primary.is-bold .navbar-menu,html.theme--documenter-dark details.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}}html.theme--documenter-dark .hero.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-link strong{color:inherit}html.theme--documenter-dark .hero.is-link .title{color:#fff}html.theme--documenter-dark .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-link .subtitle a:not(.button),html.theme--documenter-dark .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-link .navbar-menu{background-color:#1abc9c}}html.theme--documenter-dark .hero.is-link .navbar-item,html.theme--documenter-dark .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-link a.navbar-item:hover,html.theme--documenter-dark .hero.is-link a.navbar-item.is-active,html.theme--documenter-dark .hero.is-link .navbar-link:hover,html.theme--documenter-dark .hero.is-link .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-link .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-link .tabs li.is-active a{color:#1abc9c !important;opacity:1}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1abc9c}html.theme--documenter-dark .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}}html.theme--documenter-dark .hero.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-info strong{color:inherit}html.theme--documenter-dark .hero.is-info .title{color:#fff}html.theme--documenter-dark .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-info .subtitle a:not(.button),html.theme--documenter-dark .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-info .navbar-menu{background-color:#3c5dcd}}html.theme--documenter-dark .hero.is-info .navbar-item,html.theme--documenter-dark .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-info a.navbar-item:hover,html.theme--documenter-dark .hero.is-info a.navbar-item.is-active,html.theme--documenter-dark .hero.is-info .navbar-link:hover,html.theme--documenter-dark .hero.is-info .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-info .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-info .tabs li.is-active a{color:#3c5dcd !important;opacity:1}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3c5dcd}html.theme--documenter-dark .hero.is-info.is-bold{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}}html.theme--documenter-dark .hero.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-success strong{color:inherit}html.theme--documenter-dark .hero.is-success .title{color:#fff}html.theme--documenter-dark .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-success .subtitle a:not(.button),html.theme--documenter-dark .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-success .navbar-menu{background-color:#259a12}}html.theme--documenter-dark .hero.is-success .navbar-item,html.theme--documenter-dark .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-success a.navbar-item:hover,html.theme--documenter-dark .hero.is-success a.navbar-item.is-active,html.theme--documenter-dark .hero.is-success .navbar-link:hover,html.theme--documenter-dark .hero.is-success .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-success .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-success .tabs li.is-active a{color:#259a12 !important;opacity:1}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#259a12}html.theme--documenter-dark .hero.is-success.is-bold{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}}html.theme--documenter-dark .hero.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-warning strong{color:inherit}html.theme--documenter-dark .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-warning .subtitle a:not(.button),html.theme--documenter-dark .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-warning .navbar-menu{background-color:#f4c72f}}html.theme--documenter-dark .hero.is-warning .navbar-item,html.theme--documenter-dark .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning a.navbar-item:hover,html.theme--documenter-dark .hero.is-warning a.navbar-item.is-active,html.theme--documenter-dark .hero.is-warning .navbar-link:hover,html.theme--documenter-dark .hero.is-warning .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-warning .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-warning .tabs li.is-active a{color:#f4c72f !important;opacity:1}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #f09100 0%, #f4c72f 71%, #faef42 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f09100 0%, #f4c72f 71%, #faef42 100%)}}html.theme--documenter-dark .hero.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-danger strong{color:inherit}html.theme--documenter-dark .hero.is-danger .title{color:#fff}html.theme--documenter-dark .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-danger .subtitle a:not(.button),html.theme--documenter-dark .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-danger .navbar-menu{background-color:#cb3c33}}html.theme--documenter-dark .hero.is-danger .navbar-item,html.theme--documenter-dark .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-danger a.navbar-item:hover,html.theme--documenter-dark .hero.is-danger a.navbar-item.is-active,html.theme--documenter-dark .hero.is-danger .navbar-link:hover,html.theme--documenter-dark .hero.is-danger .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-danger .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-danger .tabs li.is-active a{color:#cb3c33 !important;opacity:1}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#cb3c33}html.theme--documenter-dark .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}}html.theme--documenter-dark .hero.is-small .hero-body,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--documenter-dark .hero.is-halfheight .hero-body,html.theme--documenter-dark .hero.is-fullheight .hero-body,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--documenter-dark .hero.is-halfheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .hero.is-halfheight{min-height:50vh}html.theme--documenter-dark .hero.is-fullheight{min-height:100vh}html.theme--documenter-dark .hero-video{overflow:hidden}html.theme--documenter-dark .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--documenter-dark .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-video{display:none}}html.theme--documenter-dark .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-buttons .button{display:flex}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-buttons{display:flex;justify-content:center}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--documenter-dark .hero-head,html.theme--documenter-dark .hero-foot{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-body{padding:3rem 3rem}}html.theme--documenter-dark .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--documenter-dark .section{padding:3rem 3rem}html.theme--documenter-dark .section.is-medium{padding:9rem 4.5rem}html.theme--documenter-dark .section.is-large{padding:18rem 6rem}}html.theme--documenter-dark .footer{background-color:#282f2f;padding:3rem 1.5rem 6rem}html.theme--documenter-dark hr{height:1px}html.theme--documenter-dark h6{text-transform:uppercase;letter-spacing:0.5px}html.theme--documenter-dark .hero{background-color:#343c3d}html.theme--documenter-dark a{transition:all 200ms ease}html.theme--documenter-dark .button{transition:all 200ms ease;border-width:1px;color:#fff}html.theme--documenter-dark .button.is-active,html.theme--documenter-dark .button.is-focused,html.theme--documenter-dark .button:active,html.theme--documenter-dark .button:focus{box-shadow:0 0 0 2px rgba(140,155,157,0.5)}html.theme--documenter-dark .button.is-white.is-hovered,html.theme--documenter-dark .button.is-white:hover{background-color:#fff}html.theme--documenter-dark .button.is-white.is-active,html.theme--documenter-dark .button.is-white.is-focused,html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white:focus{border-color:#fff;box-shadow:0 0 0 2px rgba(255,255,255,0.5)}html.theme--documenter-dark .button.is-black.is-hovered,html.theme--documenter-dark .button.is-black:hover{background-color:#1d1d1d}html.theme--documenter-dark .button.is-black.is-active,html.theme--documenter-dark .button.is-black.is-focused,html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black:focus{border-color:#0a0a0a;box-shadow:0 0 0 2px rgba(10,10,10,0.5)}html.theme--documenter-dark .button.is-light.is-hovered,html.theme--documenter-dark .button.is-light:hover{background-color:#fff}html.theme--documenter-dark .button.is-light.is-active,html.theme--documenter-dark .button.is-light.is-focused,html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light:focus{border-color:#ecf0f1;box-shadow:0 0 0 2px rgba(236,240,241,0.5)}html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered,html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover{background-color:#3a4344}html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused,html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus{border-color:#282f2f;box-shadow:0 0 0 2px rgba(40,47,47,0.5)}html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark details.docstring>section>a.button.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:hover{background-color:#436d9a}html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark details.docstring>section>a.button.is-active.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark details.docstring>section>a.button.is-focused.docs-sourcelink,html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark details.docstring>section>a.button.docs-sourcelink:focus{border-color:#375a7f;box-shadow:0 0 0 2px rgba(55,90,127,0.5)}html.theme--documenter-dark .button.is-link.is-hovered,html.theme--documenter-dark .button.is-link:hover{background-color:#1fdeb8}html.theme--documenter-dark .button.is-link.is-active,html.theme--documenter-dark .button.is-link.is-focused,html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link:focus{border-color:#1abc9c;box-shadow:0 0 0 2px rgba(26,188,156,0.5)}html.theme--documenter-dark .button.is-info.is-hovered,html.theme--documenter-dark .button.is-info:hover{background-color:#5a76d5}html.theme--documenter-dark .button.is-info.is-active,html.theme--documenter-dark .button.is-info.is-focused,html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info:focus{border-color:#3c5dcd;box-shadow:0 0 0 2px rgba(60,93,205,0.5)}html.theme--documenter-dark .button.is-success.is-hovered,html.theme--documenter-dark .button.is-success:hover{background-color:#2dbc16}html.theme--documenter-dark .button.is-success.is-active,html.theme--documenter-dark .button.is-success.is-focused,html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success:focus{border-color:#259a12;box-shadow:0 0 0 2px rgba(37,154,18,0.5)}html.theme--documenter-dark .button.is-warning.is-hovered,html.theme--documenter-dark .button.is-warning:hover{background-color:#f6d153}html.theme--documenter-dark .button.is-warning.is-active,html.theme--documenter-dark .button.is-warning.is-focused,html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning:focus{border-color:#f4c72f;box-shadow:0 0 0 2px rgba(244,199,47,0.5)}html.theme--documenter-dark .button.is-danger.is-hovered,html.theme--documenter-dark .button.is-danger:hover{background-color:#d35951}html.theme--documenter-dark .button.is-danger.is-active,html.theme--documenter-dark .button.is-danger.is-focused,html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger:focus{border-color:#cb3c33;box-shadow:0 0 0 2px rgba(203,60,51,0.5)}html.theme--documenter-dark .label{color:#dbdee0}html.theme--documenter-dark .button,html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .select,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea{height:2.5em}html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em}html.theme--documenter-dark .select:after,html.theme--documenter-dark .select select{border-width:1px}html.theme--documenter-dark .control.has-addons .button,html.theme--documenter-dark .control.has-addons .input,html.theme--documenter-dark .control.has-addons #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-addons form.docs-search>input,html.theme--documenter-dark .control.has-addons .select{margin-right:-1px}html.theme--documenter-dark .notification{background-color:#343c3d}html.theme--documenter-dark .card{box-shadow:none;border:1px solid #343c3d;background-color:#282f2f;border-radius:.4em}html.theme--documenter-dark .card .card-image img{border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-header{box-shadow:none;background-color:rgba(18,18,18,0.2);border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-footer{background-color:rgba(18,18,18,0.2)}html.theme--documenter-dark .card .card-footer,html.theme--documenter-dark .card .card-footer-item{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .notification.is-white a:not(.button){color:#0a0a0a;text-decoration:underline}html.theme--documenter-dark .notification.is-black a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-light a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-dark a:not(.button),html.theme--documenter-dark .content kbd.notification a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-primary a:not(.button),html.theme--documenter-dark details.docstring>section>a.notification.docs-sourcelink a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-link a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-info a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-success a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-warning a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-danger a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .tag,html.theme--documenter-dark .content kbd,html.theme--documenter-dark details.docstring>section>a.docs-sourcelink{border-radius:.4em}html.theme--documenter-dark .menu-list a{transition:all 300ms ease}html.theme--documenter-dark .modal-card-body{background-color:#282f2f}html.theme--documenter-dark .modal-card-foot,html.theme--documenter-dark .modal-card-head{border-color:#343c3d}html.theme--documenter-dark .message-header{font-weight:700;background-color:#343c3d;color:#fff}html.theme--documenter-dark .message-body{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .navbar{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent{background:none}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar .navbar-menu{background-color:#375a7f;border-radius:0 0 .4em .4em}}html.theme--documenter-dark .hero .navbar,html.theme--documenter-dark body>.navbar{border-radius:0}html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous{border-width:1px}html.theme--documenter-dark .panel-block,html.theme--documenter-dark .panel-heading,html.theme--documenter-dark .panel-tabs{border-width:1px}html.theme--documenter-dark .panel-block:first-child,html.theme--documenter-dark .panel-heading:first-child,html.theme--documenter-dark .panel-tabs:first-child{border-top-width:1px}html.theme--documenter-dark .panel-heading{font-weight:700}html.theme--documenter-dark .panel-tabs a{border-width:1px;margin-bottom:-1px}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#17a689}html.theme--documenter-dark .panel-block:hover{color:#1dd2af}html.theme--documenter-dark .panel-block:hover .panel-icon{color:#1dd2af}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#17a689}html.theme--documenter-dark .tabs a{border-bottom-width:1px;margin-bottom:-1px}html.theme--documenter-dark .tabs ul{border-bottom-width:1px}html.theme--documenter-dark .tabs.is-boxed a{border-width:1px}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#1f2424}html.theme--documenter-dark .tabs.is-toggle li a{border-width:1px;margin-bottom:0}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .hero.is-white .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-black .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-light .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-dark .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .content kbd.hero .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-primary .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark details.docstring>section>a.hero.docs-sourcelink .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-link .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-info .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-success .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-warning .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-danger .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark h1 .docs-heading-anchor,html.theme--documenter-dark h1 .docs-heading-anchor:hover,html.theme--documenter-dark h1 .docs-heading-anchor:visited,html.theme--documenter-dark h2 .docs-heading-anchor,html.theme--documenter-dark h2 .docs-heading-anchor:hover,html.theme--documenter-dark h2 .docs-heading-anchor:visited,html.theme--documenter-dark h3 .docs-heading-anchor,html.theme--documenter-dark h3 .docs-heading-anchor:hover,html.theme--documenter-dark h3 .docs-heading-anchor:visited,html.theme--documenter-dark h4 .docs-heading-anchor,html.theme--documenter-dark h4 .docs-heading-anchor:hover,html.theme--documenter-dark h4 .docs-heading-anchor:visited,html.theme--documenter-dark h5 .docs-heading-anchor,html.theme--documenter-dark h5 .docs-heading-anchor:hover,html.theme--documenter-dark h5 .docs-heading-anchor:visited,html.theme--documenter-dark h6 .docs-heading-anchor,html.theme--documenter-dark h6 .docs-heading-anchor:hover,html.theme--documenter-dark h6 .docs-heading-anchor:visited{color:#f2f2f2}html.theme--documenter-dark h1 .docs-heading-anchor-permalink,html.theme--documenter-dark h2 .docs-heading-anchor-permalink,html.theme--documenter-dark h3 .docs-heading-anchor-permalink,html.theme--documenter-dark h4 .docs-heading-anchor-permalink,html.theme--documenter-dark h5 .docs-heading-anchor-permalink,html.theme--documenter-dark h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--documenter-dark h1 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h2 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h3 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h4 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h5 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--documenter-dark h1:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h2:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h3:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h4:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h5:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--documenter-dark .docs-light-only{display:none !important}html.theme--documenter-dark pre{position:relative;overflow:hidden}html.theme--documenter-dark pre code,html.theme--documenter-dark pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--documenter-dark pre code:first-of-type,html.theme--documenter-dark pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--documenter-dark pre code:last-of-type,html.theme--documenter-dark pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--documenter-dark pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#fff;cursor:pointer;text-align:center}html.theme--documenter-dark pre .copy-button:focus,html.theme--documenter-dark pre .copy-button:hover{opacity:1;background:rgba(255,255,255,0.1);color:#1abc9c}html.theme--documenter-dark pre .copy-button.success{color:#259a12;opacity:1}html.theme--documenter-dark pre .copy-button.error{color:#cb3c33;opacity:1}html.theme--documenter-dark pre:hover .copy-button{opacity:1}html.theme--documenter-dark .link-icon:hover{color:#1abc9c}html.theme--documenter-dark .admonition{background-color:#282f2f;border-style:solid;border-width:2px;border-color:#dbdee0;border-radius:4px;font-size:1rem}html.theme--documenter-dark .admonition strong{color:currentColor}html.theme--documenter-dark .admonition.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--documenter-dark .admonition.is-medium{font-size:1.25rem}html.theme--documenter-dark .admonition.is-large{font-size:1.5rem}html.theme--documenter-dark .admonition.is-default{background-color:#282f2f;border-color:#dbdee0}html.theme--documenter-dark .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .admonition.is-default>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-info{background-color:#282f2f;border-color:#3c5dcd}html.theme--documenter-dark .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#3c5dcd}html.theme--documenter-dark .admonition.is-info>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-success{background-color:#282f2f;border-color:#259a12}html.theme--documenter-dark .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#259a12}html.theme--documenter-dark .admonition.is-success>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-warning{background-color:#282f2f;border-color:#f4c72f}html.theme--documenter-dark .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#f4c72f}html.theme--documenter-dark .admonition.is-warning>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-danger{background-color:#282f2f;border-color:#cb3c33}html.theme--documenter-dark .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#cb3c33}html.theme--documenter-dark .admonition.is-danger>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-compat{background-color:#282f2f;border-color:#3489da}html.theme--documenter-dark .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#3489da}html.theme--documenter-dark .admonition.is-compat>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-todo{background-color:#282f2f;border-color:#9558b2}html.theme--documenter-dark .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#9558b2}html.theme--documenter-dark .admonition.is-todo>.admonition-body{color:#fff}html.theme--documenter-dark .admonition-header{color:#dbdee0;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--documenter-dark .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--documenter-dark .admonition-header .admonition-anchor{opacity:0;margin-left:0.5em;font-size:0.75em;color:inherit;text-decoration:none;transition:opacity 0.2s ease-in-out}html.theme--documenter-dark .admonition-header .admonition-anchor:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--documenter-dark .admonition-header .admonition-anchor:hover{opacity:1 !important;text-decoration:none}html.theme--documenter-dark .admonition-header:hover .admonition-anchor{opacity:0.8}html.theme--documenter-dark details.admonition.is-details>.admonition-header{list-style:none}html.theme--documenter-dark details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--documenter-dark details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--documenter-dark .admonition-body{color:#fff;padding:0.5rem .75rem}html.theme--documenter-dark .admonition-body pre{background-color:#282f2f}html.theme--documenter-dark .admonition-body code{background-color:rgba(255,255,255,0.05)}html.theme--documenter-dark details.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #5e6d6f;border-radius:4px;box-shadow:none;max-width:100%}html.theme--documenter-dark details.docstring>summary{list-style-type:none;align-items:stretch;padding:0.5rem .75rem;background-color:#282f2f;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5e6d6f;overflow:auto}html.theme--documenter-dark details.docstring>summary code{background-color:transparent}html.theme--documenter-dark details.docstring>summary .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--documenter-dark details.docstring>summary .docstring-binding{margin-right:0.3em}html.theme--documenter-dark details.docstring>summary .docstring-category{margin-left:0.3em}html.theme--documenter-dark details.docstring>summary::before{content:'\f054';font-family:"Font Awesome 6 Free";font-weight:900;min-width:1.1rem;color:#2E63BD;display:inline-block}html.theme--documenter-dark details.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark details.docstring>section:last-child{border-bottom:none}html.theme--documenter-dark details.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--documenter-dark details.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--documenter-dark details.docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark details.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark details.docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--documenter-dark details.docstring[open]>summary::before{content:"\f078"}html.theme--documenter-dark .documenter-example-output{background-color:#1f2424}html.theme--documenter-dark .warning-overlay-base,html.theme--documenter-dark .dev-warning-overlay,html.theme--documenter-dark .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;padding:10px 35px;text-align:center;font-size:15px}html.theme--documenter-dark .warning-overlay-base .outdated-warning-closer,html.theme--documenter-dark .dev-warning-overlay .outdated-warning-closer,html.theme--documenter-dark .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--documenter-dark .warning-overlay-base a,html.theme--documenter-dark .dev-warning-overlay a,html.theme--documenter-dark .outdated-warning-overlay a{color:#1abc9c}html.theme--documenter-dark .warning-overlay-base a:hover,html.theme--documenter-dark .dev-warning-overlay a:hover,html.theme--documenter-dark .outdated-warning-overlay a:hover{color:#1dd2af}html.theme--documenter-dark .outdated-warning-overlay{background-color:#282f2f;color:#fff;border-bottom:3px solid rgba(0,0,0,0)}html.theme--documenter-dark .dev-warning-overlay{background-color:#282f2f;color:#fff;border-bottom:3px solid rgba(0,0,0,0)}html.theme--documenter-dark .footnote-reference{position:relative;display:inline-block}html.theme--documenter-dark .footnote-preview{display:none;position:absolute;z-index:1000;max-width:300px;width:max-content;background-color:#1f2424;border:1px solid #1dd2af;padding:10px;border-radius:5px;top:calc(100% + 10px);left:50%;transform:translateX(-50%);box-sizing:border-box;--arrow-left: 50%}html.theme--documenter-dark .footnote-preview::before{content:"";position:absolute;top:-10px;left:var(--arrow-left);transform:translateX(-50%);border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #1dd2af}html.theme--documenter-dark .content pre{border:2px solid #5e6d6f;border-radius:4px}html.theme--documenter-dark .content code{font-weight:inherit}html.theme--documenter-dark .content a code{color:#1abc9c}html.theme--documenter-dark .content a:hover code{color:#1dd2af}html.theme--documenter-dark .content h1 code,html.theme--documenter-dark .content h2 code,html.theme--documenter-dark .content h3 code,html.theme--documenter-dark .content h4 code,html.theme--documenter-dark .content h5 code,html.theme--documenter-dark .content h6 code{color:#f2f2f2}html.theme--documenter-dark .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--documenter-dark .content blockquote>ul:first-child,html.theme--documenter-dark .content blockquote>ol:first-child,html.theme--documenter-dark .content .admonition-body>ul:first-child,html.theme--documenter-dark .content .admonition-body>ol:first-child{margin-top:0}html.theme--documenter-dark pre,html.theme--documenter-dark code{font-variant-ligatures:no-contextual}html.theme--documenter-dark .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb a.is-disabled,html.theme--documenter-dark .breadcrumb a.is-disabled:hover{color:#f2f2f2}html.theme--documenter-dark .hljs{background:initial !important}html.theme--documenter-dark .katex .katex-mathml{top:0;right:0}html.theme--documenter-dark .katex-display,html.theme--documenter-dark mjx-container,html.theme--documenter-dark .MathJax_Display{margin:0.5em 0 !important}html.theme--documenter-dark html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--documenter-dark li.no-marker{list-style:none}html.theme--documenter-dark #documenter .docs-main>article{overflow-wrap:break-word}html.theme--documenter-dark #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main{width:100%}html.theme--documenter-dark #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-main>header,html.theme--documenter-dark #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar{background-color:#1f2424;border-bottom:1px solid #5e6d6f;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow:hidden}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--documenter-dark #documenter .docs-main section.footnotes{border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-main section.footnotes li .tag:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li details.docstring>section>a.docs-sourcelink:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--documenter-dark .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--documenter-dark #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5e6d6f;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--documenter-dark #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--documenter-dark #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--documenter-dark #documenter .docs-sidebar{display:flex;flex-direction:column;color:#fff;background-color:#282f2f;border-right:1px solid #5e6d6f;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--documenter-dark #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar{left:0;top:0}}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a,html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a:hover{color:#fff}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5e6d6f;display:none;padding:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5e6d6f;padding-bottom:1.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#fff;background:#282f2f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#fff;background-color:#32393a}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5e6d6f;border-bottom:1px solid #5e6d6f;background-color:#1f2424}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#1f2424;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#32393a;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"โšฌ";margin-right:0.4em}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--documenter-dark #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}html.theme--documenter-dark kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--documenter-dark .search-min-width-50{min-width:50%}html.theme--documenter-dark .search-min-height-100{min-height:100%}html.theme--documenter-dark .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms;border:1px solid transparent}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1);outline:none;border-color:#00d4aa}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .property-search-result-badge,html.theme--documenter-dark .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--documenter-dark .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--documenter-dark .search-filter:hover,html.theme--documenter-dark .search-filter:focus{color:#333}html.theme--documenter-dark .search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}html.theme--documenter-dark .search-filter-selected:hover,html.theme--documenter-dark .search-filter-selected:focus{color:#f5f5f5}html.theme--documenter-dark .search-result-highlight{background-color:#ffdd57;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .search-result-title{width:85%;color:#f5f5f5}html.theme--documenter-dark .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem}html.theme--documenter-dark .gap-8{gap:2rem}html.theme--documenter-dark{background-color:#1f2424;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark .ansi span.sgr1{font-weight:bolder}html.theme--documenter-dark .ansi span.sgr2{font-weight:lighter}html.theme--documenter-dark .ansi span.sgr3{font-style:italic}html.theme--documenter-dark .ansi span.sgr4{text-decoration:underline}html.theme--documenter-dark .ansi span.sgr7{color:#1f2424;background-color:#fff}html.theme--documenter-dark .ansi span.sgr8{color:transparent}html.theme--documenter-dark .ansi span.sgr8 span{color:transparent}html.theme--documenter-dark .ansi span.sgr9{text-decoration:line-through}html.theme--documenter-dark .ansi span.sgr30{color:#242424}html.theme--documenter-dark .ansi span.sgr31{color:#f6705f}html.theme--documenter-dark .ansi span.sgr32{color:#4fb43a}html.theme--documenter-dark .ansi span.sgr33{color:#f4c72f}html.theme--documenter-dark .ansi span.sgr34{color:#7587f0}html.theme--documenter-dark .ansi span.sgr35{color:#bc89d3}html.theme--documenter-dark .ansi span.sgr36{color:#49b6ca}html.theme--documenter-dark .ansi span.sgr37{color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr40{background-color:#242424}html.theme--documenter-dark .ansi span.sgr41{background-color:#f6705f}html.theme--documenter-dark .ansi span.sgr42{background-color:#4fb43a}html.theme--documenter-dark .ansi span.sgr43{background-color:#f4c72f}html.theme--documenter-dark .ansi span.sgr44{background-color:#7587f0}html.theme--documenter-dark .ansi span.sgr45{background-color:#bc89d3}html.theme--documenter-dark .ansi span.sgr46{background-color:#49b6ca}html.theme--documenter-dark .ansi span.sgr47{background-color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr90{color:#92a0a2}html.theme--documenter-dark .ansi span.sgr91{color:#ff8674}html.theme--documenter-dark .ansi span.sgr92{color:#79d462}html.theme--documenter-dark .ansi span.sgr93{color:#ffe76b}html.theme--documenter-dark .ansi span.sgr94{color:#8a98ff}html.theme--documenter-dark .ansi span.sgr95{color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr96{color:#6bc8db}html.theme--documenter-dark .ansi span.sgr97{color:#ecf0f1}html.theme--documenter-dark .ansi span.sgr100{background-color:#92a0a2}html.theme--documenter-dark .ansi span.sgr101{background-color:#ff8674}html.theme--documenter-dark .ansi span.sgr102{background-color:#79d462}html.theme--documenter-dark .ansi span.sgr103{background-color:#ffe76b}html.theme--documenter-dark .ansi span.sgr104{background-color:#8a98ff}html.theme--documenter-dark .ansi span.sgr105{background-color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr106{background-color:#6bc8db}html.theme--documenter-dark .ansi span.sgr107{background-color:#ecf0f1}html.theme--documenter-dark code.language-julia-repl>span.hljs-meta{color:#4fb43a;font-weight:bolder}html.theme--documenter-dark .hljs{background:#2b2b2b;color:#f8f8f2}html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-quote{color:#d4d0ab}html.theme--documenter-dark .hljs-variable,html.theme--documenter-dark .hljs-template-variable,html.theme--documenter-dark .hljs-tag,html.theme--documenter-dark .hljs-name,html.theme--documenter-dark .hljs-selector-id,html.theme--documenter-dark .hljs-selector-class,html.theme--documenter-dark .hljs-regexp,html.theme--documenter-dark .hljs-deletion{color:#ffa07a}html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-link{color:#f5ab35}html.theme--documenter-dark .hljs-attribute{color:#ffd700}html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-addition{color:#abe338}html.theme--documenter-dark .hljs-title,html.theme--documenter-dark .hljs-section{color:#00e0e0}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{color:#dcc6e0}html.theme--documenter-dark .hljs-emphasis{font-style:italic}html.theme--documenter-dark .hljs-strong{font-weight:bold}@media screen and (-ms-high-contrast: active){html.theme--documenter-dark .hljs-addition,html.theme--documenter-dark .hljs-attribute,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-link,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-quote{color:highlight}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{font-weight:bold}}html.theme--documenter-dark .hljs-subst{color:#f8f8f2}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333 !important;background-color:#f1f5f9 !important}html.theme--documenter-dark .search-result-title{color:whitesmoke}html.theme--documenter-dark .search-result-highlight{background-color:greenyellow;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem} diff --git a/save/docs/build/assets/themes/documenter-light.css b/save/docs/build/assets/themes/documenter-light.css new file mode 100644 index 00000000..babd27d6 --- /dev/null +++ b/save/docs/build/assets/themes/documenter-light.css @@ -0,0 +1,9 @@ +๏ปฟ.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.file-cta,.file-name,.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input,.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.button:focus,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-focused.button,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.button:active,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.is-active.button{outline:none}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled],.file-cta[disabled],.file-name[disabled],.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],.button[disabled],fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] .button{cursor:not-allowed}.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb,.file,.button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}.admonition:not(:last-child),.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child),.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child){margin-bottom:1.5rem}.modal-close,.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.modal-close::before,.delete::before,.modal-close::after,.delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close::before,.delete::before{height:2px;width:50%}.modal-close::after,.delete::after{height:50%;width:2px}.modal-close:hover,.delete:hover,.modal-close:focus,.delete:focus{background-color:rgba(10,10,10,0.3)}.modal-close:active,.delete:active{background-color:rgba(10,10,10,0.4)}.is-small.modal-close,#documenter .docs-sidebar form.docs-search>input.modal-close,.is-small.delete,#documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.modal-close,.is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.modal-close,.is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.modal-background,.modal,.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#4eb5de !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#27a1d2 !important}.has-background-primary{background-color:#4eb5de !important}.has-text-primary-light{color:#eef8fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c3e6f4 !important}.has-background-primary-light{background-color:#eef8fc !important}.has-text-primary-dark{color:#1a6d8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#228eb9 !important}.has-background-primary-dark{background-color:#1a6d8e !important}.has-text-link{color:#2e63b8 !important}a.has-text-link:hover,a.has-text-link:focus{color:#244d8f !important}.has-background-link{background-color:#2e63b8 !important}.has-text-link-light{color:#eff3fb !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c6d6f1 !important}.has-background-link-light{background-color:#eff3fb !important}.has-text-link-dark{color:#3169c4 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#5485d4 !important}.has-background-link-dark{background-color:#3169c4 !important}.has-text-info{color:#3c5dcd !important}a.has-text-info:hover,a.has-text-info:focus{color:#2c48aa !important}.has-background-info{background-color:#3c5dcd !important}.has-text-info-light{color:#eff2fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6d0f0 !important}.has-background-info-light{background-color:#eff2fb !important}.has-text-info-dark{color:#3253c3 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#5571d3 !important}.has-background-info-dark{background-color:#3253c3 !important}.has-text-success{color:#259a12 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a6c0d !important}.has-background-success{background-color:#259a12 !important}.has-text-success-light{color:#effded !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c7f8bf !important}.has-background-success-light{background-color:#effded !important}.has-text-success-dark{color:#2ec016 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#3fe524 !important}.has-background-success-dark{background-color:#2ec016 !important}.has-text-warning{color:#a98800 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#765f00 !important}.has-background-warning{background-color:#a98800 !important}.has-text-warning-light{color:#fffbeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fff1b8 !important}.has-background-warning-light{background-color:#fffbeb !important}.has-text-warning-dark{color:#cca400 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#ffcd00 !important}.has-background-warning-dark{background-color:#cca400 !important}.has-text-danger{color:#cb3c33 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a23029 !important}.has-background-danger{background-color:#cb3c33 !important}.has-text-danger-light{color:#fbefef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f1c8c6 !important}.has-background-danger-light{background-color:#fbefef !important}.has-text-danger-dark{color:#c03930 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#d35850 !important}.has-background-danger-dark{background-color:#c03930 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#6b6b6b !important}.has-background-grey{background-color:#6b6b6b !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,details.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}body{color:#222;font-size:1em;font-weight:400;line-height:1.5}a{color:#2e63b8;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:rgba(0,0,0,0.05);color:#000;font-size:.875em;font-weight:normal;padding:.1em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#222;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#222;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#222}@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:#bbb;color:#222;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #2e63b8}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #2e63b8}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#222;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button #documenter .docs-sidebar form.docs-search>input.icon,#documenter .docs-sidebar .button form.docs-search>input.icon,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3c5dcd;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#222;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#222}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#222}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#2e63b8;text-decoration:none}.button.is-ghost:hover,.button.is-ghost.is-hovered{color:#2e63b8;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-dark,.content kbd.button{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.content kbd.button:hover,.button.is-dark.is-hovered,.content kbd.button.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.content kbd.button:focus,.button.is-dark.is-focused,.content kbd.button.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.content kbd.button:focus:not(:active),.button.is-dark.is-focused:not(:active),.content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.button.is-dark:active,.content kbd.button:active,.button.is-dark.is-active,.content kbd.button.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],.content kbd.button[disabled],fieldset[disabled] .button.is-dark,fieldset[disabled] .content kbd.button,.content fieldset[disabled] kbd.button{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted,.content kbd.button.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.content kbd.button.is-inverted:hover,.button.is-dark.is-inverted.is-hovered,.content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],.content kbd.button.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted,fieldset[disabled] .content kbd.button.is-inverted,.content fieldset[disabled] kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after,.content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined,.content kbd.button.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.content kbd.button.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.content kbd.button.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.content kbd.button.is-outlined:focus,.button.is-dark.is-outlined.is-focused,.content kbd.button.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after,.content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.content kbd.button.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.content kbd.button.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after,.content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],.content kbd.button.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined,fieldset[disabled] .content kbd.button.is-outlined,.content fieldset[disabled] kbd.button.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined,.content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.content kbd.button.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.content kbd.button.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.content kbd.button.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused,.content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.content kbd.button.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.content kbd.button.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],.content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined,fieldset[disabled] .content kbd.button.is-inverted.is-outlined,.content fieldset[disabled] kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary,details.docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:transparent;color:#fff}.button.is-primary:hover,details.docstring>section>a.button.docs-sourcelink:hover,.button.is-primary.is-hovered,details.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#43b1dc;border-color:transparent;color:#fff}.button.is-primary:focus,details.docstring>section>a.button.docs-sourcelink:focus,.button.is-primary.is-focused,details.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),details.docstring>section>a.button.docs-sourcelink:focus:not(:active),.button.is-primary.is-focused:not(:active),details.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.button.is-primary:active,details.docstring>section>a.button.docs-sourcelink:active,.button.is-primary.is-active,details.docstring>section>a.button.is-active.docs-sourcelink{background-color:#39acda;border-color:transparent;color:#fff}.button.is-primary[disabled],details.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary,fieldset[disabled] details.docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;box-shadow:none}.button.is-primary.is-inverted,details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted:hover,details.docstring>section>a.button.is-inverted.docs-sourcelink:hover,.button.is-primary.is-inverted.is-hovered,details.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],details.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted,fieldset[disabled] details.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#4eb5de}.button.is-primary.is-loading::after,details.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined,details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;color:#4eb5de}.button.is-primary.is-outlined:hover,details.docstring>section>a.button.is-outlined.docs-sourcelink:hover,.button.is-primary.is-outlined.is-hovered,details.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-outlined:focus,details.docstring>section>a.button.is-outlined.docs-sourcelink:focus,.button.is-primary.is-outlined.is-focused,details.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.button.is-primary.is-outlined.is-loading::after,details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-outlined.is-loading:hover::after,details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,details.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-outlined.is-loading:focus::after,details.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after,details.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],details.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-outlined,fieldset[disabled] details.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;box-shadow:none;color:#4eb5de}.button.is-primary.is-inverted.is-outlined,details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,details.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-inverted.is-outlined:focus,details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,.button.is-primary.is-inverted.is-outlined.is-focused,details.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,details.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,details.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-inverted.is-outlined[disabled],details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined,fieldset[disabled] details.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light,details.docstring>section>a.button.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.button.is-primary.is-light:hover,details.docstring>section>a.button.is-light.docs-sourcelink:hover,.button.is-primary.is-light.is-hovered,details.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e3f3fa;border-color:transparent;color:#1a6d8e}.button.is-primary.is-light:active,details.docstring>section>a.button.is-light.docs-sourcelink:active,.button.is-primary.is-light.is-active,details.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d8eff8;border-color:transparent;color:#1a6d8e}.button.is-link{background-color:#2e63b8;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#2b5eae;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2958a4;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#2e63b8;border-color:#2e63b8;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#2e63b8}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;color:#2e63b8}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;box-shadow:none;color:#2e63b8}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff3fb;color:#3169c4}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#e4ecf8;border-color:transparent;color:#3169c4}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#dae5f6;border-color:transparent;color:#3169c4}.button.is-info{background-color:#3c5dcd;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#3355c9;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.button.is-info:active,.button.is-info.is-active{background-color:#3151bf;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3c5dcd;border-color:#3c5dcd;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3c5dcd}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3c5dcd}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;color:#3c5dcd}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;box-shadow:none;color:#3c5dcd}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3c5dcd}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff2fb;color:#3253c3}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e5e9f8;border-color:transparent;color:#3253c3}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#dae1f6;border-color:transparent;color:#3253c3}.button.is-success{background-color:#259a12;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#228f11;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.button.is-success:active,.button.is-success.is-active{background-color:#20830f;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#259a12;border-color:#259a12;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#259a12}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#259a12}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#259a12;color:#259a12}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#259a12;border-color:#259a12;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #259a12 #259a12 !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;box-shadow:none;color:#259a12}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#259a12}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #259a12 #259a12 !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effded;color:#2ec016}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e5fce1;border-color:transparent;color:#2ec016}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#dbfad6;border-color:transparent;color:#2ec016}.button.is-warning{background-color:#a98800;border-color:transparent;color:#fff}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#9c7d00;border-color:transparent;color:#fff}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:#fff}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#8f7300;border-color:transparent;color:#fff}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#a98800;border-color:#a98800;box-shadow:none}.button.is-warning.is-inverted{background-color:#fff;color:#a98800}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#a98800}.button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#a98800;color:#a98800}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#a98800;border-color:#a98800;color:#fff}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #a98800 #a98800 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#a98800;box-shadow:none;color:#a98800}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#a98800}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a98800 #a98800 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-warning.is-light{background-color:#fffbeb;color:#cca400}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff9de;border-color:transparent;color:#cca400}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff6d1;border-color:transparent;color:#cca400}.button.is-danger{background-color:#cb3c33;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#c13930;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#b7362e;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#cb3c33;border-color:#cb3c33;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#cb3c33}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#cb3c33}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;color:#cb3c33}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#cb3c33;border-color:#cb3c33;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;box-shadow:none;color:#cb3c33}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#cb3c33}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#fbefef;color:#c03930}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#f8e6e5;border-color:transparent;color:#c03930}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#f6dcda;border-color:transparent;color:#c03930}.button.is-small,#documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}.button.is-small:not(.is-rounded),#documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#6b6b6b;box-shadow:none;pointer-events:none}.button.is-rounded,#documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:0.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){.container{max-width:992px}}@media screen and (max-width: 1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:0.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#222;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:0.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:0.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:0.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:0.8em}.content h5{font-size:1.125em;margin-bottom:0.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}.content ol.is-lower-roman:not([type]){list-style-type:lower-roman}.content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}.content ol.is-upper-roman:not([type]){list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.content table th{color:#222}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#222}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#222}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small,#documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small,#documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image,#documenter .docs-sidebar .docs-logo>img{display:block;position:relative}.image img,#documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}.image img.is-rounded,#documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}.image.is-fullwidth,#documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,#documenter .docs-sidebar .docs-logo>img.is-square,.image.is-1by1,#documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}.image.is-5by4,#documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}.image.is-4by3,#documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}.image.is-3by2,#documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}.image.is-5by3,#documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}.image.is-16by9,#documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}.image.is-2by1,#documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}.image.is-3by1,#documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}.image.is-4by5,#documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}.image.is-3by4,#documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}.image.is-2by3,#documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}.image.is-3by5,#documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}.image.is-9by16,#documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}.image.is-1by2,#documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}.image.is-1by3,#documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}.image.is-16x16,#documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}.image.is-24x24,#documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}.image.is-32x32,#documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}.image.is-48x48,#documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}.image.is-64x64,#documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}.image.is-96x96,#documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}.image.is-128x128,#documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:0.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.notification.is-dark,.content kbd.notification{background-color:#363636;color:#fff}.notification.is-primary,details.docstring>section>a.notification.docs-sourcelink{background-color:#4eb5de;color:#fff}.notification.is-primary.is-light,details.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.notification.is-link{background-color:#2e63b8;color:#fff}.notification.is-link.is-light{background-color:#eff3fb;color:#3169c4}.notification.is-info{background-color:#3c5dcd;color:#fff}.notification.is-info.is-light{background-color:#eff2fb;color:#3253c3}.notification.is-success{background-color:#259a12;color:#fff}.notification.is-success.is-light{background-color:#effded;color:#2ec016}.notification.is-warning{background-color:#a98800;color:#fff}.notification.is-warning.is-light{background-color:#fffbeb;color:#cca400}.notification.is-danger{background-color:#cb3c33;color:#fff}.notification.is-danger.is-light{background-color:#fbefef;color:#c03930}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#222}.progress::-moz-progress-bar{background-color:#222}.progress::-ms-fill{background-color:#222;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value,.content kbd.progress::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar,.content kbd.progress::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill,.content kbd.progress::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate,.content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value,details.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#4eb5de}.progress.is-primary::-moz-progress-bar,details.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#4eb5de}.progress.is-primary::-ms-fill,details.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#4eb5de}.progress.is-primary:indeterminate,details.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #4eb5de 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#2e63b8}.progress.is-link::-moz-progress-bar{background-color:#2e63b8}.progress.is-link::-ms-fill{background-color:#2e63b8}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #2e63b8 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3c5dcd}.progress.is-info::-moz-progress-bar{background-color:#3c5dcd}.progress.is-info::-ms-fill{background-color:#3c5dcd}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #3c5dcd 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#259a12}.progress.is-success::-moz-progress-bar{background-color:#259a12}.progress.is-success::-ms-fill{background-color:#259a12}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #259a12 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#a98800}.progress.is-warning::-moz-progress-bar{background-color:#a98800}.progress.is-warning::-ms-fill{background-color:#a98800}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #a98800 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#cb3c33}.progress.is-danger::-moz-progress-bar{background-color:#cb3c33}.progress.is-danger::-ms-fill{background-color:#cb3c33}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #cb3c33 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, #222 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small,#documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#222}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.table td.is-link,.table th.is-link{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.table td.is-info,.table th.is-info{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}.table td.is-success,.table th.is-success{background-color:#259a12;border-color:#259a12;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#a98800;border-color:#a98800;color:#fff}.table td.is-danger,.table th.is-danger{background-color:#cb3c33;border-color:#cb3c33;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#4eb5de;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#222}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#4eb5de;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:rgba(0,0,0,0)}.table thead td,.table thead th{border-width:0 0 2px;color:#222}.table tfoot{background-color:rgba(0,0,0,0)}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#222}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:0.25em 0.5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag,.tags .content kbd,.content .tags kbd,.tags details.docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}.tags .tag:not(:last-child),.tags .content kbd:not(:last-child),.content .tags kbd:not(:last-child),.tags details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large),.tags.are-medium .content kbd:not(.is-normal):not(.is-large),.content .tags.are-medium kbd:not(.is-normal):not(.is-large),.tags.are-medium details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium),.tags.are-large .content kbd:not(.is-normal):not(.is-medium),.content .tags.are-large kbd:not(.is-normal):not(.is-medium),.tags.are-large details.docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag,.tags.is-centered .content kbd,.content .tags.is-centered kbd,.tags.is-centered details.docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child),.tags.is-right .content kbd:not(:first-child),.content .tags.is-right kbd:not(:first-child),.tags.is-right details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}.tags.is-right .tag:not(:last-child),.tags.is-right .content kbd:not(:last-child),.content .tags.is-right kbd:not(:last-child),.tags.is-right details.docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}.tags.has-addons .tag,.tags.has-addons .content kbd,.content .tags.has-addons kbd,.tags.has-addons details.docstring>section>a.docs-sourcelink{margin-right:0}.tags.has-addons .tag:not(:first-child),.tags.has-addons .content kbd:not(:first-child),.content .tags.has-addons kbd:not(:first-child),.tags.has-addons details.docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child),.tags.has-addons .content kbd:not(:last-child),.content .tags.has-addons kbd:not(:last-child),.tags.has-addons details.docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body),.content kbd:not(body),details.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#222;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.tag:not(body) .delete,.content kbd:not(body) .delete,details.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag.is-white:not(body),.content kbd.is-white:not(body),details.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}.tag.is-black:not(body),.content kbd.is-black:not(body),details.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}.tag.is-light:not(body),.content kbd.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.tag.is-dark:not(body),.content kbd:not(body),details.docstring>section>a.docs-sourcelink.is-dark:not(body),.content details.docstring>section>kbd:not(body){background-color:#363636;color:#fff}.tag.is-primary:not(body),.content kbd.is-primary:not(body),details.docstring>section>a.docs-sourcelink:not(body){background-color:#4eb5de;color:#fff}.tag.is-primary.is-light:not(body),.content kbd.is-primary.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#eef8fc;color:#1a6d8e}.tag.is-link:not(body),.content kbd.is-link:not(body),details.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#2e63b8;color:#fff}.tag.is-link.is-light:not(body),.content kbd.is-link.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#eff3fb;color:#3169c4}.tag.is-info:not(body),.content kbd.is-info:not(body),details.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#3c5dcd;color:#fff}.tag.is-info.is-light:not(body),.content kbd.is-info.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#eff2fb;color:#3253c3}.tag.is-success:not(body),.content kbd.is-success:not(body),details.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#259a12;color:#fff}.tag.is-success.is-light:not(body),.content kbd.is-success.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#effded;color:#2ec016}.tag.is-warning:not(body),.content kbd.is-warning:not(body),details.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#a98800;color:#fff}.tag.is-warning.is-light:not(body),.content kbd.is-warning.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffbeb;color:#cca400}.tag.is-danger:not(body),.content kbd.is-danger:not(body),details.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#cb3c33;color:#fff}.tag.is-danger.is-light:not(body),.content kbd.is-danger.is-light:not(body),details.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fbefef;color:#c03930}.tag.is-normal:not(body),.content kbd.is-normal:not(body),details.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}.tag.is-medium:not(body),.content kbd.is-medium:not(body),details.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}.tag.is-large:not(body),.content kbd.is-large:not(body),details.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child),.content kbd:not(body) .icon:first-child:not(:last-child),details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child),.content kbd:not(body) .icon:last-child:not(:first-child),details.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child,.content kbd:not(body) .icon:first-child:last-child,details.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag.is-delete:not(body),.content kbd.is-delete:not(body),details.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,details.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,details.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}.tag.is-delete:not(body):hover,.content kbd.is-delete:not(body):hover,details.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,.tag.is-delete:not(body):focus,.content kbd.is-delete:not(body):focus,details.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#e8e8e8}.tag.is-delete:not(body):active,.content kbd.is-delete:not(body):active,details.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#dbdbdb}.tag.is-rounded:not(body),#documenter .docs-sidebar form.docs-search>input:not(body),.content kbd.is-rounded:not(body),#documenter .docs-sidebar .content form.docs-search>input:not(body),details.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}a.tag:hover,details.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.title .content kbd,.content .title kbd,.title details.docstring>section>a.docs-sourcelink,.subtitle .tag,.subtitle .content kbd,.content .subtitle kbd,.subtitle details.docstring>section>a.docs-sourcelink{vertical-align:middle}.title{color:#222;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#222;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#222;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#222}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#707070}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#707070}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#707070}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#707070}.select select:hover,.textarea:hover,.input:hover,#documenter .docs-sidebar form.docs-search>input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input,#documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#b5b5b5}.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{border-color:#2e63b8;box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#6b6b6b}.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,.input[disabled]::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,.input[disabled]::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-webkit-input-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,.input[disabled]:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,.input[disabled]:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-ms-input-placeholder{color:rgba(107,107,107,0.3)}.textarea,.input,#documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}.textarea[readonly],.input[readonly],#documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}.is-white.textarea,.is-white.input,#documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,#documenter .docs-sidebar form.docs-search>input.is-white:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-white.textarea:active,.is-white.input:active,#documenter .docs-sidebar form.docs-search>input.is-white:active,.is-white.is-active.textarea,.is-white.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.is-black.textarea,.is-black.input,#documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}.is-black.textarea:focus,.is-black.input:focus,#documenter .docs-sidebar form.docs-search>input.is-black:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-black.textarea:active,.is-black.input:active,#documenter .docs-sidebar form.docs-search>input.is-black:active,.is-black.is-active.textarea,.is-black.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.is-light.textarea,.is-light.input,#documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,#documenter .docs-sidebar form.docs-search>input.is-light:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-light.textarea:active,.is-light.input:active,#documenter .docs-sidebar form.docs-search>input.is-light:active,.is-light.is-active.textarea,.is-light.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.is-dark.textarea,.content kbd.textarea,.is-dark.input,#documenter .docs-sidebar form.docs-search>input.is-dark,.content kbd.input{border-color:#363636}.is-dark.textarea:focus,.content kbd.textarea:focus,.is-dark.input:focus,#documenter .docs-sidebar form.docs-search>input.is-dark:focus,.content kbd.input:focus,.is-dark.is-focused.textarea,.content kbd.is-focused.textarea,.is-dark.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.content kbd.is-focused.input,#documenter .docs-sidebar .content form.docs-search>input.is-focused,.is-dark.textarea:active,.content kbd.textarea:active,.is-dark.input:active,#documenter .docs-sidebar form.docs-search>input.is-dark:active,.content kbd.input:active,.is-dark.is-active.textarea,.content kbd.is-active.textarea,.is-dark.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.content kbd.is-active.input,#documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.is-primary.textarea,details.docstring>section>a.textarea.docs-sourcelink,.is-primary.input,#documenter .docs-sidebar form.docs-search>input.is-primary,details.docstring>section>a.input.docs-sourcelink{border-color:#4eb5de}.is-primary.textarea:focus,details.docstring>section>a.textarea.docs-sourcelink:focus,.is-primary.input:focus,#documenter .docs-sidebar form.docs-search>input.is-primary:focus,details.docstring>section>a.input.docs-sourcelink:focus,.is-primary.is-focused.textarea,details.docstring>section>a.is-focused.textarea.docs-sourcelink,.is-primary.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,details.docstring>section>a.is-focused.input.docs-sourcelink,.is-primary.textarea:active,details.docstring>section>a.textarea.docs-sourcelink:active,.is-primary.input:active,#documenter .docs-sidebar form.docs-search>input.is-primary:active,details.docstring>section>a.input.docs-sourcelink:active,.is-primary.is-active.textarea,details.docstring>section>a.is-active.textarea.docs-sourcelink,.is-primary.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,details.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.is-link.textarea,.is-link.input,#documenter .docs-sidebar form.docs-search>input.is-link{border-color:#2e63b8}.is-link.textarea:focus,.is-link.input:focus,#documenter .docs-sidebar form.docs-search>input.is-link:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-link.textarea:active,.is-link.input:active,#documenter .docs-sidebar form.docs-search>input.is-link:active,.is-link.is-active.textarea,.is-link.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.is-info.textarea,.is-info.input,#documenter .docs-sidebar form.docs-search>input.is-info{border-color:#3c5dcd}.is-info.textarea:focus,.is-info.input:focus,#documenter .docs-sidebar form.docs-search>input.is-info:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-info.textarea:active,.is-info.input:active,#documenter .docs-sidebar form.docs-search>input.is-info:active,.is-info.is-active.textarea,.is-info.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.is-success.textarea,.is-success.input,#documenter .docs-sidebar form.docs-search>input.is-success{border-color:#259a12}.is-success.textarea:focus,.is-success.input:focus,#documenter .docs-sidebar form.docs-search>input.is-success:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-success.textarea:active,.is-success.input:active,#documenter .docs-sidebar form.docs-search>input.is-success:active,.is-success.is-active.textarea,.is-success.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.is-warning.textarea,.is-warning.input,#documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#a98800}.is-warning.textarea:focus,.is-warning.input:focus,#documenter .docs-sidebar form.docs-search>input.is-warning:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-warning.textarea:active,.is-warning.input:active,#documenter .docs-sidebar form.docs-search>input.is-warning:active,.is-warning.is-active.textarea,.is-warning.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.is-danger.textarea,.is-danger.input,#documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#cb3c33}.is-danger.textarea:focus,.is-danger.input:focus,#documenter .docs-sidebar form.docs-search>input.is-danger:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-danger.textarea:active,.is-danger.input:active,#documenter .docs-sidebar form.docs-search>input.is-danger:active,.is-danger.is-active.textarea,.is-danger.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.is-small.textarea,.is-small.input,#documenter .docs-sidebar form.docs-search>input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input,#documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}.is-large.textarea,.is-large.input,#documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input,#documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}.is-inline.textarea,.is-inline.input,#documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}.input.is-rounded,#documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}.input.is-static,#documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#222}.radio[disabled],.checkbox[disabled],fieldset[disabled] .radio,fieldset[disabled] .checkbox,.radio input[disabled],.checkbox input[disabled]{color:#6b6b6b;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#2e63b8;right:1.125em;z-index:4}.select.is-rounded select,#documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:0.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#222}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.select.is-dark:not(:hover)::after,.content kbd.select:not(:hover)::after{border-color:#363636}.select.is-dark select,.content kbd.select select{border-color:#363636}.select.is-dark select:hover,.content kbd.select select:hover,.select.is-dark select.is-hovered,.content kbd.select select.is-hovered{border-color:#292929}.select.is-dark select:focus,.content kbd.select select:focus,.select.is-dark select.is-focused,.content kbd.select select.is-focused,.select.is-dark select:active,.content kbd.select select:active,.select.is-dark select.is-active,.content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.select.is-primary:not(:hover)::after,details.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#4eb5de}.select.is-primary select,details.docstring>section>a.select.docs-sourcelink select{border-color:#4eb5de}.select.is-primary select:hover,details.docstring>section>a.select.docs-sourcelink select:hover,.select.is-primary select.is-hovered,details.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#39acda}.select.is-primary select:focus,details.docstring>section>a.select.docs-sourcelink select:focus,.select.is-primary select.is-focused,details.docstring>section>a.select.docs-sourcelink select.is-focused,.select.is-primary select:active,details.docstring>section>a.select.docs-sourcelink select:active,.select.is-primary select.is-active,details.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.select.is-link:not(:hover)::after{border-color:#2e63b8}.select.is-link select{border-color:#2e63b8}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2958a4}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select.is-info:not(:hover)::after{border-color:#3c5dcd}.select.is-info select{border-color:#3c5dcd}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#3151bf}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.select.is-success:not(:hover)::after{border-color:#259a12}.select.is-success select{border-color:#259a12}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#20830f}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.select.is-warning:not(:hover)::after{border-color:#a98800}.select.is-warning select{border-color:#a98800}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#8f7300}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.select.is-danger:not(:hover)::after{border-color:#cb3c33}.select.is-danger select{border-color:#cb3c33}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#b7362e}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.select.is-small,#documenter .docs-sidebar form.docs-search>input.select{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#6b6b6b !important;opacity:0.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}.select.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-dark .file-cta,.content kbd.file .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.content kbd.file:hover .file-cta,.file.is-dark.is-hovered .file-cta,.content kbd.file.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.content kbd.file:focus .file-cta,.file.is-dark.is-focused .file-cta,.content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,54,54,0.25);color:#fff}.file.is-dark:active .file-cta,.content kbd.file:active .file-cta,.file.is-dark.is-active .file-cta,.content kbd.file.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta,details.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#4eb5de;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,details.docstring>section>a.file.docs-sourcelink:hover .file-cta,.file.is-primary.is-hovered .file-cta,details.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#43b1dc;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,details.docstring>section>a.file.docs-sourcelink:focus .file-cta,.file.is-primary.is-focused .file-cta,details.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(78,181,222,0.25);color:#fff}.file.is-primary:active .file-cta,details.docstring>section>a.file.docs-sourcelink:active .file-cta,.file.is-primary.is-active .file-cta,details.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#39acda;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#2e63b8;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#2b5eae;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(46,99,184,0.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2958a4;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3c5dcd;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#3355c9;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(60,93,205,0.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#3151bf;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#259a12;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#228f11;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(37,154,18,0.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#20830f;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#a98800;border-color:transparent;color:#fff}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#9c7d00;border-color:transparent;color:#fff}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(169,136,0,0.25);color:#fff}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#8f7300;border-color:transparent;color:#fff}.file.is-danger .file-cta{background-color:#cb3c33;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#c13930;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(203,60,51,0.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#b7362e;border-color:transparent;color:#fff}.file.is-small,#documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa,#documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#222}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#222}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#222}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#222;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:0.5em}.label.is-small,#documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:0.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark,.content kbd.help{color:#363636}.help.is-primary,details.docstring>section>a.help.docs-sourcelink{color:#4eb5de}.help.is-link{color:#2e63b8}.help.is-info{color:#3c5dcd}.help.is-success{color:#259a12}.help.is-warning{color:#a98800}.help.is-danger{color:#cb3c33}.field:not(:last-child){margin-bottom:0.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button.is-hovered:not([disabled]),.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,.field.has-addons .control .input.is-hovered:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button.is-focused:not([disabled]),.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button.is-active:not([disabled]),.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,.field.has-addons .control .input.is-focused:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,.field.has-addons .control .input.is-active:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select.is-focused:not([disabled]),.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select.is-active:not([disabled]){z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button.is-focused:not([disabled]):hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button.is-active:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,.field.has-addons .control .input.is-focused:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,.field.has-addons .control .input.is-active:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select.is-focused:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small,#documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}.field-label.is-normal{padding-top:0.375em}.field-label.is-medium{font-size:1.25rem;padding-top:0.375em}.field-label.is-large{font-size:1.5rem;padding-top:0.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#222}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}.control.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#2e63b8;display:initial;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#222;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small,#documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:#bbb;color:#222;max-width:100%;position:relative}.card-footer:first-child,.card-content:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-footer:last-child,.card-content:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}.card-header-title{align-items:center;color:#222;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:rgba(0,0,0,0);padding:1.5rem}.card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:#bbb;padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#222;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#2e63b8;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,0.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,0.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small,#documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#222;display:block;padding:0.5em 0.75em}.menu-list a:hover{background-color:#f5f5f5;color:#222}.menu-list a.is-active{background-color:#2e63b8;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#6b6b6b;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small,#documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark,.content kbd.message{background-color:#fafafa}.message.is-dark .message-header,.content kbd.message .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body,.content kbd.message .message-body{border-color:#363636}.message.is-primary,details.docstring>section>a.message.docs-sourcelink{background-color:#eef8fc}.message.is-primary .message-header,details.docstring>section>a.message.docs-sourcelink .message-header{background-color:#4eb5de;color:#fff}.message.is-primary .message-body,details.docstring>section>a.message.docs-sourcelink .message-body{border-color:#4eb5de;color:#1a6d8e}.message.is-link{background-color:#eff3fb}.message.is-link .message-header{background-color:#2e63b8;color:#fff}.message.is-link .message-body{border-color:#2e63b8;color:#3169c4}.message.is-info{background-color:#eff2fb}.message.is-info .message-header{background-color:#3c5dcd;color:#fff}.message.is-info .message-body{border-color:#3c5dcd;color:#3253c3}.message.is-success{background-color:#effded}.message.is-success .message-header{background-color:#259a12;color:#fff}.message.is-success .message-body{border-color:#259a12;color:#2ec016}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#a98800;color:#fff}.message.is-warning .message-body{border-color:#a98800;color:#cca400}.message.is-danger{background-color:#fbefef}.message.is-danger .message-header{background-color:#cb3c33;color:#fff}.message.is-danger .message-body{border-color:#cb3c33;color:#c03930}.message-header{align-items:center;background-color:#222;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#222;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:rgba(0,0,0,0)}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,0.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#222;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}.navbar.is-dark,.content kbd.navbar{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.content kbd.navbar .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link,.content kbd.navbar .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.content kbd.navbar .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.content kbd.navbar .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.content kbd.navbar .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.content kbd.navbar .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.content kbd.navbar .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active,.content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after,.content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger,.content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-dark .navbar-start>.navbar-item,.content kbd.navbar .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.content kbd.navbar .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.content kbd.navbar .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link,.content kbd.navbar .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.content kbd.navbar .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.content kbd.navbar .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.content kbd.navbar .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.content kbd.navbar .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.content kbd.navbar .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.content kbd.navbar .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.content kbd.navbar .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.content kbd.navbar .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.content kbd.navbar .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.content kbd.navbar .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.content kbd.navbar .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active,.content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.content kbd.navbar .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after,.content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active,.content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary,details.docstring>section>a.navbar.docs-sourcelink{background-color:#4eb5de;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after,details.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger,details.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-primary .navbar-start>.navbar-item,details.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,details.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link,details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,details.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after,details.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,details.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active,details.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#4eb5de;color:#fff}}.navbar.is-link{background-color:#2e63b8;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#2e63b8;color:#fff}}.navbar.is-info{background-color:#3c5dcd;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3c5dcd;color:#fff}}.navbar.is-success{background-color:#259a12;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20830f;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20830f;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20830f;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#259a12;color:#fff}}.navbar.is-warning{background-color:#a98800;color:#fff}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:#fff}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:#fff}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#a98800;color:#fff}}.navbar.is-danger{background-color:#cb3c33;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#cb3c33;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#222;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,0.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#222;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#2e63b8}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8}.navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8;border-bottom-style:solid;border-bottom-width:3px;color:#2e63b8;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#2e63b8;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1056px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small,#documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,.pagination.is-rounded .pagination-next,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#222;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3c5dcd}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}.pagination-previous[disabled],.pagination-previous.is-disabled,.pagination-next[disabled],.pagination-next.is-disabled,.pagination-link[disabled],.pagination-link.is-disabled{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#6b6b6b;opacity:0.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:#bbb;font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading,.content kbd.panel .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active,.content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon,.content kbd.panel .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading,details.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#4eb5de;color:#fff}.panel.is-primary .panel-tabs a.is-active,details.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#4eb5de}.panel.is-primary .panel-block.is-active .panel-icon,details.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#4eb5de}.panel.is-link .panel-heading{background-color:#2e63b8;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#2e63b8}.panel.is-link .panel-block.is-active .panel-icon{color:#2e63b8}.panel.is-info .panel-heading{background-color:#3c5dcd;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3c5dcd}.panel.is-info .panel-block.is-active .panel-icon{color:#3c5dcd}.panel.is-success .panel-heading{background-color:#259a12;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#259a12}.panel.is-success .panel-block.is-active .panel-icon{color:#259a12}.panel.is-warning .panel-heading{background-color:#a98800;color:#fff}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#a98800}.panel.is-warning .panel-block.is-active .panel-icon{color:#a98800}.panel.is-danger .panel-heading{background-color:#cb3c33;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#cb3c33}.panel.is-danger .panel-block.is-active .panel-icon{color:#cb3c33}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#222;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:0.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#222}.panel-list a:hover{color:#2e63b8}.panel-block{align-items:center;color:#222;display:flex;justify-content:flex-start;padding:0.5em 0.75em}.panel-block input[type="checkbox"]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#2e63b8;color:#363636}.panel-block.is-active .panel-icon{color:#2e63b8}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#6b6b6b;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#222;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#222;color:#222}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#2e63b8;color:#2e63b8}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:0.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:rgba(0,0,0,0) !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#2e63b8;border-color:#2e63b8;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small,#documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>.column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>.column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>.column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>.column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333337%}.column.is-offset-1-mobile{margin-left:8.33333337%}.column.is-2-mobile{flex:none;width:16.66666674%}.column.is-offset-2-mobile{margin-left:16.66666674%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333337%}.column.is-offset-4-mobile{margin-left:33.33333337%}.column.is-5-mobile{flex:none;width:41.66666674%}.column.is-offset-5-mobile{margin-left:41.66666674%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333337%}.column.is-offset-7-mobile{margin-left:58.33333337%}.column.is-8-mobile{flex:none;width:66.66666674%}.column.is-offset-8-mobile{margin-left:66.66666674%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333337%}.column.is-offset-10-mobile{margin-left:83.33333337%}.column.is-11-mobile{flex:none;width:91.66666674%}.column.is-offset-11-mobile{margin-left:91.66666674%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333337%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333337%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66666674%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66666674%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333337%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333337%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66666674%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66666674%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333337%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333337%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66666674%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66666674%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333337%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333337%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66666674%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66666674%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333337%}.column.is-offset-1-touch{margin-left:8.33333337%}.column.is-2-touch{flex:none;width:16.66666674%}.column.is-offset-2-touch{margin-left:16.66666674%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333337%}.column.is-offset-4-touch{margin-left:33.33333337%}.column.is-5-touch{flex:none;width:41.66666674%}.column.is-offset-5-touch{margin-left:41.66666674%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333337%}.column.is-offset-7-touch{margin-left:58.33333337%}.column.is-8-touch{flex:none;width:66.66666674%}.column.is-offset-8-touch{margin-left:66.66666674%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333337%}.column.is-offset-10-touch{margin-left:83.33333337%}.column.is-11-touch{flex:none;width:91.66666674%}.column.is-offset-11-touch{margin-left:91.66666674%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333337%}.column.is-offset-1-desktop{margin-left:8.33333337%}.column.is-2-desktop{flex:none;width:16.66666674%}.column.is-offset-2-desktop{margin-left:16.66666674%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333337%}.column.is-offset-4-desktop{margin-left:33.33333337%}.column.is-5-desktop{flex:none;width:41.66666674%}.column.is-offset-5-desktop{margin-left:41.66666674%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333337%}.column.is-offset-7-desktop{margin-left:58.33333337%}.column.is-8-desktop{flex:none;width:66.66666674%}.column.is-offset-8-desktop{margin-left:66.66666674%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333337%}.column.is-offset-10-desktop{margin-left:83.33333337%}.column.is-11-desktop{flex:none;width:91.66666674%}.column.is-offset-11-desktop{margin-left:91.66666674%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333337%}.column.is-offset-1-widescreen{margin-left:8.33333337%}.column.is-2-widescreen{flex:none;width:16.66666674%}.column.is-offset-2-widescreen{margin-left:16.66666674%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333337%}.column.is-offset-4-widescreen{margin-left:33.33333337%}.column.is-5-widescreen{flex:none;width:41.66666674%}.column.is-offset-5-widescreen{margin-left:41.66666674%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333337%}.column.is-offset-7-widescreen{margin-left:58.33333337%}.column.is-8-widescreen{flex:none;width:66.66666674%}.column.is-offset-8-widescreen{margin-left:66.66666674%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333337%}.column.is-offset-10-widescreen{margin-left:83.33333337%}.column.is-11-widescreen{flex:none;width:91.66666674%}.column.is-offset-11-widescreen{margin-left:91.66666674%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.33333337%}.column.is-offset-1-fullhd{margin-left:8.33333337%}.column.is-2-fullhd{flex:none;width:16.66666674%}.column.is-offset-2-fullhd{margin-left:16.66666674%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333337%}.column.is-offset-4-fullhd{margin-left:33.33333337%}.column.is-5-fullhd{flex:none;width:41.66666674%}.column.is-offset-5-fullhd{margin-left:41.66666674%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333337%}.column.is-offset-7-fullhd{margin-left:58.33333337%}.column.is-8-fullhd{flex:none;width:66.66666674%}.column.is-offset-8-fullhd{margin-left:66.66666674%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333337%}.column.is-offset-10-fullhd{margin-left:83.33333337%}.column.is-11-fullhd{flex:none;width:91.66666674%}.column.is-offset-11-fullhd{margin-left:91.66666674%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: .25rem}}.columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: .5rem}}.columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: .75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333337%}.tile.is-2{flex:none;width:16.66666674%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333337%}.tile.is-5{flex:none;width:41.66666674%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333337%}.tile.is-8{flex:none;width:66.66666674%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333337%}.tile.is-11{flex:none;width:91.66666674%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,0.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,0.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:0.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,0.7)}.hero.is-light .subtitle{color:rgba(0,0,0,0.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}.hero.is-dark,.content kbd.hero{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong,.content kbd.hero strong{color:inherit}.hero.is-dark .title,.content kbd.hero .title{color:#fff}.hero.is-dark .subtitle,.content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}.hero.is-dark .subtitle a:not(.button),.content kbd.hero .subtitle a:not(.button),.hero.is-dark .subtitle strong,.content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-dark .navbar-menu,.content kbd.hero .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.content kbd.hero .navbar-item,.hero.is-dark .navbar-link,.content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-dark a.navbar-item:hover,.content kbd.hero a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.content kbd.hero a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.content kbd.hero .navbar-link:hover,.hero.is-dark .navbar-link.is-active,.content kbd.hero .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a,.content kbd.hero .tabs a{color:#fff;opacity:0.9}.hero.is-dark .tabs a:hover,.content kbd.hero .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a,.content kbd.hero .tabs li.is-active a{color:#363636 !important;opacity:1}.hero.is-dark .tabs.is-boxed a,.content kbd.hero .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a,.content kbd.hero .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.content kbd.hero .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover,.content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.content kbd.hero .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.content kbd.hero .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold,.content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu,.content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary,details.docstring>section>a.hero.docs-sourcelink{background-color:#4eb5de;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),details.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong,details.docstring>section>a.hero.docs-sourcelink strong{color:inherit}.hero.is-primary .title,details.docstring>section>a.hero.docs-sourcelink .title{color:#fff}.hero.is-primary .subtitle,details.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}.hero.is-primary .subtitle a:not(.button),details.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),.hero.is-primary .subtitle strong,details.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-primary .navbar-menu,details.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#4eb5de}}.hero.is-primary .navbar-item,details.docstring>section>a.hero.docs-sourcelink .navbar-item,.hero.is-primary .navbar-link,details.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-primary a.navbar-item:hover,details.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,details.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,details.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,.hero.is-primary .navbar-link.is-active,details.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#39acda;color:#fff}.hero.is-primary .tabs a,details.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}.hero.is-primary .tabs a:hover,details.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a,details.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#4eb5de !important;opacity:1}.hero.is-primary .tabs.is-boxed a,details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a,details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover,details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,details.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,details.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#4eb5de}.hero.is-primary.is-bold,details.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu,details.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}}.hero.is-link{background-color:#2e63b8;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,0.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-link .navbar-menu{background-color:#2e63b8}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2958a4;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:0.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#2e63b8 !important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#2e63b8}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}}.hero.is-info{background-color:#3c5dcd;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,0.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-info .navbar-menu{background-color:#3c5dcd}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#3151bf;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:0.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3c5dcd !important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3c5dcd}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}}.hero.is-success{background-color:#259a12;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,0.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-success .navbar-menu{background-color:#259a12}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#20830f;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:0.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#259a12 !important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#259a12}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}}.hero.is-warning{background-color:#a98800;color:#fff}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:#fff}.hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-warning .navbar-menu{background-color:#a98800}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#8f7300;color:#fff}.hero.is-warning .tabs a{color:#fff;opacity:0.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#a98800 !important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:#fff}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#a98800}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #764b00 0%, #a98800 71%, #c2bd00 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #764b00 0%, #a98800 71%, #c2bd00 100%)}}.hero.is-danger{background-color:#cb3c33;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-danger .navbar-menu{background-color:#cb3c33}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#b7362e;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:0.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#cb3c33 !important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#cb3c33}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}}.hero.is-small .hero-body,#documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}h1 .docs-heading-anchor,h1 .docs-heading-anchor:hover,h1 .docs-heading-anchor:visited,h2 .docs-heading-anchor,h2 .docs-heading-anchor:hover,h2 .docs-heading-anchor:visited,h3 .docs-heading-anchor,h3 .docs-heading-anchor:hover,h3 .docs-heading-anchor:visited,h4 .docs-heading-anchor,h4 .docs-heading-anchor:hover,h4 .docs-heading-anchor:visited,h5 .docs-heading-anchor,h5 .docs-heading-anchor:hover,h5 .docs-heading-anchor:visited,h6 .docs-heading-anchor,h6 .docs-heading-anchor:hover,h6 .docs-heading-anchor:visited{color:#222}h1 .docs-heading-anchor-permalink,h2 .docs-heading-anchor-permalink,h3 .docs-heading-anchor-permalink,h4 .docs-heading-anchor-permalink,h5 .docs-heading-anchor-permalink,h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}h1 .docs-heading-anchor-permalink::before,h2 .docs-heading-anchor-permalink::before,h3 .docs-heading-anchor-permalink::before,h4 .docs-heading-anchor-permalink::before,h5 .docs-heading-anchor-permalink::before,h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}h1:hover .docs-heading-anchor-permalink,h2:hover .docs-heading-anchor-permalink,h3:hover .docs-heading-anchor-permalink,h4:hover .docs-heading-anchor-permalink,h5:hover .docs-heading-anchor-permalink,h6:hover .docs-heading-anchor-permalink{visibility:visible}.docs-dark-only{display:none !important}pre{position:relative;overflow:hidden}pre code,pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}pre code:first-of-type,pre code.hljs:first-of-type{padding-top:0.5rem !important}pre code:last-of-type,pre code.hljs:last-of-type{padding-bottom:0.5rem !important}pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#222;cursor:pointer;text-align:center}pre .copy-button:focus,pre .copy-button:hover{opacity:1;background:rgba(34,34,34,0.1);color:#2e63b8}pre .copy-button.success{color:#259a12;opacity:1}pre .copy-button.error{color:#cb3c33;opacity:1}pre:hover .copy-button{opacity:1}.link-icon:hover{color:#2e63b8}.admonition{background-color:#f5f5f5;border-style:solid;border-width:2px;border-color:#4a4a4a;border-radius:4px;font-size:1rem}.admonition strong{color:currentColor}.admonition.is-small,#documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}.admonition.is-medium{font-size:1.25rem}.admonition.is-large{font-size:1.5rem}.admonition.is-default{background-color:#f5f5f5;border-color:#4a4a4a}.admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#4a4a4a}.admonition.is-default>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-info{background-color:#f5f5f5;border-color:#3c5dcd}.admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#3c5dcd}.admonition.is-info>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-success{background-color:#f5f5f5;border-color:#259a12}.admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#259a12}.admonition.is-success>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-warning{background-color:#f5f5f5;border-color:#a98800}.admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#a98800}.admonition.is-warning>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-danger{background-color:#f5f5f5;border-color:#cb3c33}.admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#cb3c33}.admonition.is-danger>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-compat{background-color:#f5f5f5;border-color:#3489da}.admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#3489da}.admonition.is-compat>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-todo{background-color:#f5f5f5;border-color:#9558b2}.admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#9558b2}.admonition.is-todo>.admonition-body{color:rgba(0,0,0,0.7)}.admonition-header{color:#4a4a4a;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}.admonition-header .admonition-anchor{opacity:0;margin-left:0.5em;font-size:0.75em;color:inherit;text-decoration:none;transition:opacity 0.2s ease-in-out}.admonition-header .admonition-anchor:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}.admonition-header .admonition-anchor:hover{opacity:1 !important;text-decoration:none}.admonition-header:hover .admonition-anchor{opacity:0.8}details.admonition.is-details>.admonition-header{list-style:none}details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}.admonition-body{color:#222;padding:0.5rem .75rem}.admonition-body pre{background-color:#f5f5f5}.admonition-body code{background-color:rgba(0,0,0,0.05)}details.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #dbdbdb;border-radius:4px;box-shadow:2px 2px 3px rgba(10,10,10,0.1);max-width:100%}details.docstring>summary{list-style-type:none;align-items:stretch;padding:0.5rem .75rem;background-color:#f5f5f5;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #dbdbdb;overflow:auto}details.docstring>summary code{background-color:transparent}details.docstring>summary .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}details.docstring>summary .docstring-binding{margin-right:0.3em}details.docstring>summary .docstring-category{margin-left:0.3em}details.docstring>summary::before{content:'\f054';font-family:"Font Awesome 6 Free";font-weight:900;min-width:1.1rem;color:#2E63BD;display:inline-block}details.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #dbdbdb}details.docstring>section:last-child{border-bottom:none}details.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}details.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}details.docstring:hover>section>a.docs-sourcelink{opacity:0.2}details.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}details.docstring>section:hover a.docs-sourcelink{opacity:1}details.docstring[open]>summary::before{content:"\f078"}.documenter-example-output{background-color:#fff}.warning-overlay-base,.dev-warning-overlay,.outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;padding:10px 35px;text-align:center;font-size:15px}.warning-overlay-base .outdated-warning-closer,.dev-warning-overlay .outdated-warning-closer,.outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}.warning-overlay-base a,.dev-warning-overlay a,.outdated-warning-overlay a{color:#2e63b8}.warning-overlay-base a:hover,.dev-warning-overlay a:hover,.outdated-warning-overlay a:hover{color:#363636}.outdated-warning-overlay{background-color:#f5f5f5;color:rgba(0,0,0,0.7);border-bottom:3px solid rgba(0,0,0,0)}.dev-warning-overlay{background-color:#f5f5f5;color:rgba(0,0,0,0.7);border-bottom:3px solid rgba(0,0,0,0)}.footnote-reference{position:relative;display:inline-block}.footnote-preview{display:none;position:absolute;z-index:1000;max-width:300px;width:max-content;background-color:#fff;border:1px solid #363636;padding:10px;border-radius:5px;top:calc(100% + 10px);left:50%;transform:translateX(-50%);box-sizing:border-box;--arrow-left: 50%}.footnote-preview::before{content:"";position:absolute;top:-10px;left:var(--arrow-left);transform:translateX(-50%);border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #363636}.content pre{border:2px solid #dbdbdb;border-radius:4px}.content code{font-weight:inherit}.content a code{color:#2e63b8}.content a:hover code{color:#363636}.content h1 code,.content h2 code,.content h3 code,.content h4 code,.content h5 code,.content h6 code{color:#222}.content table{display:block;width:initial;max-width:100%;overflow-x:auto}.content blockquote>ul:first-child,.content blockquote>ol:first-child,.content .admonition-body>ul:first-child,.content .admonition-body>ol:first-child{margin-top:0}pre,code{font-variant-ligatures:no-contextual}.breadcrumb a.is-disabled{cursor:default;pointer-events:none}.breadcrumb a.is-disabled,.breadcrumb a.is-disabled:hover{color:#222}.hljs{background:initial !important}.katex .katex-mathml{top:0;right:0}.katex-display,mjx-container,.MathJax_Display{margin:0.5em 0 !important}html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}li.no-marker{list-style:none}#documenter .docs-main>article{overflow-wrap:break-word}#documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){#documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){#documenter .docs-main{width:100%}#documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}#documenter .docs-main>header,#documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}#documenter .docs-main header.docs-navbar{background-color:#fff;border-bottom:1px solid #dbdbdb;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}#documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow:hidden}#documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}#documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}#documenter .docs-main header.docs-navbar .docs-right .docs-icon,#documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}#documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}#documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}#documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #bbb;transition-duration:0.7s;-webkit-transition-duration:0.7s}#documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}#documenter .docs-main section.footnotes{border-top:1px solid #dbdbdb}#documenter .docs-main section.footnotes li .tag:first-child,#documenter .docs-main section.footnotes li details.docstring>section>a.docs-sourcelink:first-child,#documenter .docs-main section.footnotes li .content kbd:first-child,.content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}#documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #dbdbdb;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){#documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}#documenter .docs-main .docs-footer .docs-footer-nextpage,#documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}#documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}#documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}#documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}#documenter .docs-sidebar{display:flex;flex-direction:column;color:#0a0a0a;background-color:#f5f5f5;border-right:1px solid #dbdbdb;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}#documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #bbb}@media screen and (min-width: 1056px){#documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){#documenter .docs-sidebar{left:0;top:0}}#documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}#documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}#documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}#documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}#documenter .docs-sidebar .docs-package-name a,#documenter .docs-sidebar .docs-package-name a:hover{color:#0a0a0a}#documenter .docs-sidebar .docs-version-selector{border-top:1px solid #dbdbdb;display:none;padding:0.5rem}#documenter .docs-sidebar .docs-version-selector.visible{display:flex}#documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #dbdbdb;padding-bottom:1.5rem}#documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}#documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}#documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}#documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}#documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}#documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}#documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}#documenter .docs-sidebar ul.docs-menu .tocitem,#documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#0a0a0a;background:#f5f5f5}#documenter .docs-sidebar ul.docs-menu a.tocitem:hover,#documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#0a0a0a;background-color:#ebebeb}#documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb;background-color:#fff}#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#fff;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#ebebeb;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}#documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"โšฌ";margin-right:0.4em}#documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}#documenter .docs-sidebar form.docs-search>input{width:14.4rem}#documenter .docs-sidebar #documenter-search-query{color:#707070;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){#documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#ccc}}@media screen and (max-width: 1055px){#documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#ccc}}kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(0,0,0,0.6);box-shadow:0 2px 0 1px rgba(0,0,0,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}.search-min-width-50{min-width:50%}.search-min-height-100{min-height:100%}.search-modal-card-body{max-height:calc(100vh - 15rem)}.search-result-link{border-radius:0.7em;transition:all 300ms;border:1px solid transparent}.search-result-link:hover,.search-result-link:focus{background-color:rgba(0,128,128,0.1);outline:none;border-color:#1DD2AF}.search-result-link .property-search-result-badge,.search-result-link .search-filter{transition:all 300ms}.property-search-result-badge,.search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}.search-result-link:hover .property-search-result-badge,.search-result-link:hover .search-filter,.search-result-link:focus .property-search-result-badge,.search-result-link:focus .search-filter{color:#f1f5f9;background-color:#333}.search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}.search-filter:hover,.search-filter:focus{color:#333}.search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}.search-filter-selected:hover,.search-filter-selected:focus{color:#f5f5f5}.search-result-highlight{background-color:#ffdd57;color:black}.search-divider{border-bottom:1px solid #dbdbdb}.search-result-title{width:85%;color:#333}.search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}#search-modal .modal-card-body::-webkit-scrollbar,#search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}#search-modal .modal-card-body::-webkit-scrollbar-thumb,#search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}#search-modal .modal-card-body::-webkit-scrollbar-track,#search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}.w-100{width:100%}.gap-2{gap:0.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.ansi span.sgr1{font-weight:bolder}.ansi span.sgr2{font-weight:lighter}.ansi span.sgr3{font-style:italic}.ansi span.sgr4{text-decoration:underline}.ansi span.sgr7{color:#fff;background-color:#222}.ansi span.sgr8{color:transparent}.ansi span.sgr8 span{color:transparent}.ansi span.sgr9{text-decoration:line-through}.ansi span.sgr30{color:#242424}.ansi span.sgr31{color:#a7201f}.ansi span.sgr32{color:#066f00}.ansi span.sgr33{color:#856b00}.ansi span.sgr34{color:#2149b0}.ansi span.sgr35{color:#7d4498}.ansi span.sgr36{color:#007989}.ansi span.sgr37{color:gray}.ansi span.sgr40{background-color:#242424}.ansi span.sgr41{background-color:#a7201f}.ansi span.sgr42{background-color:#066f00}.ansi span.sgr43{background-color:#856b00}.ansi span.sgr44{background-color:#2149b0}.ansi span.sgr45{background-color:#7d4498}.ansi span.sgr46{background-color:#007989}.ansi span.sgr47{background-color:gray}.ansi span.sgr90{color:#616161}.ansi span.sgr91{color:#cb3c33}.ansi span.sgr92{color:#0e8300}.ansi span.sgr93{color:#a98800}.ansi span.sgr94{color:#3c5dcd}.ansi span.sgr95{color:#9256af}.ansi span.sgr96{color:#008fa3}.ansi span.sgr97{color:#f5f5f5}.ansi span.sgr100{background-color:#616161}.ansi span.sgr101{background-color:#cb3c33}.ansi span.sgr102{background-color:#0e8300}.ansi span.sgr103{background-color:#a98800}.ansi span.sgr104{background-color:#3c5dcd}.ansi span.sgr105{background-color:#9256af}.ansi span.sgr106{background-color:#008fa3}.ansi span.sgr107{background-color:#f5f5f5}code.language-julia-repl>span.hljs-meta{color:#066f00;font-weight:bolder}/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org> + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#F3F3F3;color:#444}.hljs-comment{color:#697070}.hljs-tag,.hljs-punctuation{color:#444a}.hljs-tag .hljs-name,.hljs-tag .hljs-attr{color:#444}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta .hljs-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-operator,.hljs-selector-pseudo{color:#ab5656}.hljs-literal{color:#695}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}.gap-4{gap:1rem} diff --git a/save/docs/build/assets/themeswap.js b/save/docs/build/assets/themeswap.js new file mode 100644 index 00000000..9f5eebe6 --- /dev/null +++ b/save/docs/build/assets/themeswap.js @@ -0,0 +1,84 @@ +// Small function to quickly swap out themes. Gets put into the <head> tag.. +function set_theme_from_local_storage() { + // Initialize the theme to null, which means default + var theme = null; + // If the browser supports the localstorage and is not disabled then try to get the + // documenter theme + if (window.localStorage != null) { + // Get the user-picked theme from localStorage. May be `null`, which means the default + // theme. + theme = window.localStorage.getItem("documenter-theme"); + } + // Check if the users preference is for dark color scheme + var darkPreference = + window.matchMedia("(prefers-color-scheme: dark)").matches === true; + // Initialize a few variables for the loop: + // + // - active: will contain the index of the theme that should be active. Note that there + // is no guarantee that localStorage contains sane values. If `active` stays `null` + // we either could not find the theme or it is the default (primary) theme anyway. + // Either way, we then need to stick to the primary theme. + // + // - disabled: style sheets that should be disabled (i.e. all the theme style sheets + // that are not the currently active theme) + var active = null; + var disabled = []; + var primaryLightTheme = null; + var primaryDarkTheme = null; + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The <link> tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // To distinguish the default (primary) theme, it needs to have the data-theme-primary + // attribute set. + if (ss.ownerNode.getAttribute("data-theme-primary") !== null) { + primaryLightTheme = themename; + } + // Check if the theme is primary dark theme so that we could store its name in darkTheme + if (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null) { + primaryDarkTheme = themename; + } + // If we find a matching theme (and it's not the default), we'll set active to non-null + if (themename === theme) active = i; + // Store the style sheets of inactive themes so that we could disable them + if (themename !== theme) disabled.push(ss); + } + var activeTheme = null; + if (active !== null) { + // If we did find an active theme, we'll (1) add the theme--$(theme) class to <html> + document.getElementsByTagName("html")[0].className = "theme--" + theme; + activeTheme = theme; + } else { + // If we did _not_ find an active theme, then we need to fall back to the primary theme + // which can either be dark or light, depending on the user's OS preference. + var activeTheme = darkPreference ? primaryDarkTheme : primaryLightTheme; + // In case it somehow happens that the relevant primary theme was not found in the + // preceding loop, we abort without doing anything. + if (activeTheme === null) { + console.error("Unable to determine primary theme."); + return; + } + // When switching to the primary light theme, then we must not have a class name + // for the <html> tag. That's only for non-primary or the primary dark theme. + if (darkPreference) { + document.getElementsByTagName("html")[0].className = + "theme--" + activeTheme; + } else { + document.getElementsByTagName("html")[0].className = ""; + } + } + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The <link> tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // we'll disable all the stylesheets, except for the active one + ss.disabled = !(themename == activeTheme); + } +} +set_theme_from_local_storage(); diff --git a/save/docs/build/assets/warner.js b/save/docs/build/assets/warner.js new file mode 100644 index 00000000..891cd539 --- /dev/null +++ b/save/docs/build/assets/warner.js @@ -0,0 +1,68 @@ +function maybeAddWarning() { + // DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE + // in siteinfo.js. DOCUMENTER_IS_DEV_VERSION is optional and defined in siteinfo.js. + // If the required variables are undefined something went horribly wrong, so we abort. + if ( + window.DOCUMENTER_NEWEST === undefined || + window.DOCUMENTER_CURRENT_VERSION === undefined || + window.DOCUMENTER_STABLE === undefined + ) { + return; + } + + // Current version is not a version number, so we can't tell if it's the newest version. Abort. + if (!/v(\d+\.)*\d+/.test(window.DOCUMENTER_CURRENT_VERSION)) { + return; + } + + // Current version is newest version, so no need to add a warning. + if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) { + return; + } + + // Add a noindex meta tag (unless one exists) so that search engines don't index this version of the docs. + if (document.body.querySelector('meta[name="robots"]') === null) { + const meta = document.createElement("meta"); + meta.name = "robots"; + meta.content = "noindex"; + + document.getElementsByTagName("head")[0].appendChild(meta); + } + + const div = document.createElement("div"); + // Base class is added by default + div.classList.add("warning-overlay-base"); + const closer = document.createElement("button"); + closer.classList.add("outdated-warning-closer", "delete"); + closer.addEventListener("click", function () { + document.body.removeChild(div); + }); + const href = window.documenterBaseURL + "/../" + window.DOCUMENTER_STABLE; + + // Determine if this is a development version or an older release + let warningMessage = ""; + if (window.DOCUMENTER_IS_DEV_VERSION === true) { + div.classList.add("dev-warning-overlay"); + warningMessage = + "This documentation is for the <strong>development version</strong> and may contain unstable or unreleased features.<br>"; + } else { + div.classList.add("outdated-warning-overlay"); + warningMessage = + "This documentation is for an <strong>older version</strong> that may be missing recent changes.<br>"; + } + + warningMessage += + '<a href="' + + href + + '">Click here to go to the documentation for the latest stable release.</a>'; + + div.innerHTML = warningMessage; + div.appendChild(closer); + document.body.appendChild(div); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", maybeAddWarning); +} else { + maybeAddWarning(); +} diff --git a/save/docs/build/concatenation.html b/save/docs/build/concatenation.html new file mode 100644 index 00000000..376244a9 --- /dev/null +++ b/save/docs/build/concatenation.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Concatenation ยท CTFlows.jl

Concatenation

Index

Warning

In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

julia> using CTFlows
+julia> x = 1
+julia> private_fun(x) # throw an error

must be replaced by

julia> using CTFlows
+julia> x = 1
+julia> CTFlows.private_fun(x)

However, if the method is reexported by another package, then, there is no need of prefixing.

julia> module OptimalControl
+           import CTFlows: private_fun
+           export private_fun
+       end
+julia> using OptimalControl
+julia> x = 1
+julia> private_fun(x)

Documentation

Base.:* โ€” Method
*(
+    F::CTFlowsODE.AbstractFlow,
+    g::Tuple{Real, Any, TF<:CTFlowsODE.AbstractFlow}
+) -> Any
+

Shorthand for concatenate(F, g) when g is a tuple (t_switch, ฮท_switch, G) including a jump.

Arguments

  • F::AbstractFlow: The first flow.
  • g::Tuple{ctNumber, Any, AbstractFlow}: Tuple with switching time, jump value, and second flow.

Returns

  • A flow with a jump at t_switch and a switch from F to G.

Example

julia> F * (1.0, ฮท, G)
source
Base.:* โ€” Method
*(
+    F::CTFlowsODE.AbstractFlow,
+    g::Tuple{Real, TF<:CTFlowsODE.AbstractFlow}
+) -> Any
+

Shorthand for concatenate(F, g) when g is a tuple (t_switch, G).

Arguments

  • F::AbstractFlow: The first flow.
  • g::Tuple{ctNumber, AbstractFlow}: Tuple containing the switching time and second flow.

Returns

  • A new flow that switches from F to G at t_switch.

Example

julia> F * (1.0, G)
source
CTFlowsODE.__concat_feedback_control โ€” Method
__concat_feedback_control(
+    F::CTFlowsODE.AbstractFlow,
+    G::CTFlowsODE.AbstractFlow,
+    t_switch::Real
+) -> CTFlows.ControlLaw{CTFlowsODE.var"#_feedback_control#__concat_feedback_control##0"{F, G, t_switch}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {F, G, t_switch}
+

Concatenate feedback control laws of two optimal control flows.

Arguments

  • F, G: OptimalControlFlow instances.
  • t_switch::Time: Switching time.

Returns

  • A ControlLaw that dispatches to F or G depending on t.

Example

julia> u = __concat_feedback_control(F, G, 2.0)
+julia> u(1.5, x, u, v)  # from F
+julia> u(2.5, x, u, v)  # from G
source
CTFlowsODE.__concat_jumps โ€” Function
__concat_jumps(
+    F::CTFlowsODE.AbstractFlow,
+    G::CTFlowsODE.AbstractFlow
+) -> Any
+__concat_jumps(
+    F::CTFlowsODE.AbstractFlow,
+    G::CTFlowsODE.AbstractFlow,
+    jump::Union{Nothing, Tuple{Real, Any}}
+) -> Any
+

Concatenate the jumps of two flows, with optional extra jump at t_switch.

Arguments

  • F, G: Flows with jump events.
  • jump: Optional tuple (t_switch, ฮท_switch) to insert.

Returns

  • Combined list of jumps.

Example

julia> __concat_jumps(F, G)
+julia> __concat_jumps(F, G, (1.0, ฮท))
source
CTFlowsODE.__concat_rhs โ€” Method
__concat_rhs(
+    F::CTFlowsODE.ODEFlow,
+    G::CTFlowsODE.ODEFlow,
+    t_switch::Real
+) -> CTFlowsODE.var"#__concat_rhs##3#__concat_rhs##4"{CTFlowsODE.ODEFlow, CTFlowsODE.ODEFlow, <:Real}
+

Concatenate ODE right-hand sides with a switch at t_switch.

Arguments

  • F, G: ODEFlow instances.
  • t_switch::Time: Time at which to switch between flows.

Returns

  • A function of the form (x, v, t) -> ....

Example

julia> rhs = __concat_rhs(F, G, 0.5)
+julia> rhs(x, v, 0.4)  # F.rhs
+julia> rhs(x, v, 0.6)  # G.rhs
source
CTFlowsODE.__concat_rhs โ€” Method
__concat_rhs(
+    F::CTFlowsODE.VectorFieldFlow,
+    G::CTFlowsODE.VectorFieldFlow,
+    t_switch::Real
+) -> CTFlowsODE.var"#__concat_rhs##1#__concat_rhs##2"{CTFlowsODE.VectorFieldFlow, CTFlowsODE.VectorFieldFlow, <:Real}
+

Concatenate vector field right-hand sides with time-based switching.

Arguments

  • F, G: VectorFieldFlow instances.
  • t_switch::Time: Switching time.

Returns

  • A function of the form (x, v, t) -> ....

Example

julia> rhs = __concat_rhs(F, G, 2.0)
+julia> rhs(x, v, 1.0)  # uses F.rhs
+julia> rhs(x, v, 3.0)  # uses G.rhs
source
CTFlowsODE.__concat_rhs โ€” Method
__concat_rhs(
+    F::CTFlowsODE.AbstractFlow{D, U},
+    G::CTFlowsODE.AbstractFlow{D, U},
+    t_switch::Real
+) -> CTFlowsODE.var"#__concat_rhs##1#__concat_rhs##2"{CTFlowsODE.VectorFieldFlow, CTFlowsODE.VectorFieldFlow, <:Real}
+

Concatenate the right-hand sides of two flows F and G, switching at time t_switch.

Arguments

  • F, G: Two flows of the same type.
  • t_switch::Time: The switching time.

Returns

  • A function rhs! that dispatches to F.rhs! before t_switch, and to G.rhs! after.

Example

julia> rhs = __concat_rhs(F, G, 1.0)
+julia> rhs!(du, u, p, 0.5)  # uses F.rhs!
+julia> rhs!(du, u, p, 1.5)  # uses G.rhs!
source
CTFlowsODE.__concat_tstops โ€” Method
__concat_tstops(
+    F::CTFlowsODE.AbstractFlow,
+    G::CTFlowsODE.AbstractFlow,
+    t_switch::Real
+) -> Any
+

Concatenate the tstops (discontinuity times) of two flows and add the switching time.

Arguments

  • F, G: Flows with tstops vectors.
  • t_switch::Time: Switching time to include.

Returns

  • A sorted vector of unique tstops.

Example

julia> __concat_tstops(F, G, 1.0)
source
CTFlowsODE.concatenate โ€” Method
concatenate(
+    F::CTFlowsODE.AbstractFlow,
+    g::Tuple{Real, Any, TF<:CTFlowsODE.AbstractFlow}
+) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
+

Concatenate two AbstractFlows and insert a jump at the switching time.

Arguments

  • F::AbstractFlow
  • g::Tuple{ctNumber,Any,AbstractFlow}: (t_switch, ฮท_switch, G)

Returns

  • A concatenated flow with the jump included.

Example

julia> F * (1.0, ฮท, G)
source
CTFlowsODE.concatenate โ€” Method
concatenate(
+    F::CTFlowsODE.OptimalControlFlow,
+    g::Tuple{Real, Any, TF<:CTFlowsODE.OptimalControlFlow}
+) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
+

Concatenate two OptimalControlFlows and a jump at switching time.

Arguments

  • F::OptimalControlFlow
  • g::Tuple{ctNumber,Any,OptimalControlFlow}

Returns

  • A combined flow with jump and control law switching.

Example

julia> F * (1.0, ฮท, G)
source
CTFlowsODE.concatenate โ€” Method
concatenate(
+    F::CTFlowsODE.AbstractFlow,
+    g::Tuple{Real, TF<:CTFlowsODE.AbstractFlow}
+) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
+

Concatenate two AbstractFlow instances with a prescribed switching time.

Arguments

  • F::AbstractFlow: First flow.
  • g::Tuple{ctNumber,AbstractFlow}: Switching time and second flow.

Returns

  • A new flow that transitions from F to G at t_switch.

Example

julia> F * (1.0, G)
source
CTFlowsODE.concatenate โ€” Method
concatenate(
+    F::CTFlowsODE.OptimalControlFlow,
+    g::Tuple{Real, TF<:CTFlowsODE.OptimalControlFlow}
+) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
+

Concatenate two OptimalControlFlows at a switching time.

Arguments

  • F::OptimalControlFlow
  • g::Tuple{ctNumber,OptimalControlFlow}

Returns

  • A combined flow with switched dynamics and feedback control.

Example

julia> F * (1.0, G)
source
diff --git a/save/docs/build/ctflows.html b/save/docs/build/ctflows.html new file mode 100644 index 00000000..54e7a73b --- /dev/null +++ b/save/docs/build/ctflows.html @@ -0,0 +1,17 @@ + +CTFlows ยท CTFlows.jl

CTFlows

Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    diff --git a/save/docs/build/ctflowsode.html b/save/docs/build/ctflowsode.html new file mode 100644 index 00000000..fbf8d4e8 --- /dev/null +++ b/save/docs/build/ctflowsode.html @@ -0,0 +1,17 @@ + +CTFlowsODE ยท CTFlows.jl

    CTFlowsODE

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlowsODE.CoTangent โ€” Type

    Alias for CTFlows.ctVector, representing cotangent vectors in continuous-time systems.

    Used for denoting adjoint states or costates in optimal control formulations.

    source
    CTFlowsODE.DCoTangent โ€” Type

    Alias for CTFlows.ctVector, representing derivative cotangent vectors.

    Useful in contexts where second-order information or directional derivatives of costates are required.

    source
    CTFlowsODE.AbstractFlow โ€” Type

    Abstract supertype for continuous-time flows.

    AbstractFlow{D,U} defines the interface for any flow system with:

    • D: the type of the differential (typically a vector or matrix),
    • U: the type of the state variable.

    Subtypes should define at least a right-hand side function for the system's dynamics.

    source
    CTFlowsODE.__autonomous โ€” Function

    Alias for CTFlows.__autonomous, a tag indicating that a flow is autonomous.

    Used internally to specify behavior in constructors or when composing flows.

    source
    CTFlowsODE.__create_hamiltonian โ€” Function

    Alias for CTFlows.__create_hamiltonian.

    Constructs the Hamiltonian function for a given continuous-time optimal control problem. This internal function typically takes an objective, dynamics, and control constraints.

    source
    CTFlowsODE.__variable โ€” Function

    Alias for CTFlows.__variable, a tag indicating that a flow depends on external variables or is non-autonomous.

    Used to distinguish time-dependent systems or flows with control/state parameterization.

    source
    CTFlowsODE.ctgradient โ€” Function

    Alias for CTFlows.ctgradient, a method to compute the gradient of a scalar function with respect to a state.

    It dispatches appropriately depending on whether the input is a scalar or a vector, and uses ForwardDiff.jl.

    source
    CTFlowsODE.rg โ€” Method

    Creates a range i:j, unless i == j, in which case returns i as an integer.

    Useful when indexing or slicing arrays with optional single-element flexibility.

    source
    diff --git a/save/docs/build/default.html b/save/docs/build/default.html new file mode 100644 index 00000000..9e68c11f --- /dev/null +++ b/save/docs/build/default.html @@ -0,0 +1,26 @@ + +Default ยท CTFlows.jl

    Default

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.__autonomous โ€” Method
    __autonomous(
    +    _::CTModels.OCP.Model{CTModels.OCP.Autonomous}
    +) -> Bool
    +

    Return true for a model declared as CTModels.Autonomous.

    Used to determine whether a model has time-independent dynamics.

    source
    CTFlows.__autonomous โ€” Method
    __autonomous(
    +    _::CTModels.OCP.Model{CTModels.OCP.NonAutonomous}
    +) -> Bool
    +

    Return false for a model declared as CTModels.NonAutonomous.

    Used to identify models whose dynamics depend explicitly on time.

    source
    CTFlows.__autonomous โ€” Method
    __autonomous() -> Bool
    +

    Return true by default, assuming the problem is autonomous.

    This is the fallback for generic cases when no model is provided.

    source
    CTFlows.__variable โ€” Method
    __variable(ocp::CTModels.OCP.Model) -> Bool
    +

    Return true if the model has one or more external variables.

    Used to check whether the problem is parameterized by an external vector v.

    source
    CTFlows.__variable โ€” Method
    __variable() -> Bool
    +

    Return false by default, assuming no external variable is used.

    Fallback for cases where no model is given.

    source
    diff --git a/save/docs/build/differential_geometry.html b/save/docs/build/differential_geometry.html new file mode 100644 index 00000000..a9f17433 --- /dev/null +++ b/save/docs/build/differential_geometry.html @@ -0,0 +1,216 @@ + +Differential Geometry ยท CTFlows.jl

    Differential Geometry

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.:โ…‹ โ€” Method

    "Directional derivative" of a vector field in the autonomous case, used internally for computing the Lie bracket.

    Example:

    julia> X = VectorField(x -> [x[2], -x[1]])
    +julia> Y = VectorField(x -> [x[1], x[2]])
    +julia> (X โ…‹ Y)([1, 2])
    +[2, -1]
    source
    CTFlows.:โ…‹ โ€” Method

    "Directional derivative" of a vector field in the nonautonomous case, used internally for computing the Lie bracket.

    Example:

    julia> X = VectorField((t, x, v) -> [t + v[1] + v[2] + x[2], -x[1]], NonFixed, NonAutonomous)
    +julia> Y = VectorField((t, x, v) ->  [v[1] + v[2] + x[1], x[2]], NonFixed, NonAutonomous)
    +julia> (X โ…‹ Y)(1, [1, 2], [2, 3])
    +[8, -1]
    source
    CTFlows.:โ‹… โ€” Method

    Lie derivative of a scalar function along a vector field in the autonomous case.

    Example:

    julia> ฯ† = x -> [x[2], -x[1]]
    +julia> X = VectorField(ฯ†)
    +julia> f = x -> x[1]^2 + x[2]^2
    +julia> (Xโ‹…f)([1, 2])
    +0
    source
    CTFlows.:โ‹… โ€” Method

    Lie derivative of a scalar function along a vector field in the nonautonomous case.

    Example:

    julia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]
    +julia> X = VectorField(ฯ†, NonAutonomous, NonFixed)
    +julia> f = (t, x, v) -> t + x[1]^2 + x[2]^2
    +julia> (Xโ‹…f)(1, [1, 2], [2, 1])
    +10
    source
    CTFlows.:โ‹… โ€” Method

    Lie derivative of a scalar function along a function (considered autonomous and non-variable).

    Example:

    julia> ฯ† = x -> [x[2], -x[1]]
    +julia> f = x -> x[1]^2 + x[2]^2
    +julia> (ฯ†โ‹…f)([1, 2])
    +0
    +julia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]
    +julia> f = (t, x, v) -> t + x[1]^2 + x[2]^2
    +julia> (ฯ†โ‹…f)(1, [1, 2], [2, 1])
    +MethodError
    source
    CTFlows.Lie โ€” Method

    Lie derivative of a scalar function along a vector field.

    Example:

    julia> ฯ† = x -> [x[2], -x[1]]
    +julia> X = VectorField(ฯ†)
    +julia> f = x -> x[1]^2 + x[2]^2
    +julia> Lie(X,f)([1, 2])
    +0
    +julia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]
    +julia> X = VectorField(ฯ†, NonAutonomous, NonFixed)
    +julia> f = (t, x, v) -> t + x[1]^2 + x[2]^2
    +julia> Lie(X, f)(1, [1, 2], [2, 1])
    +10
    source
    CTFlows.Lie โ€” Method

    Lie derivative of a scalar function along a function with specified dependencies.

    Example:

    julia> ฯ† = x -> [x[2], -x[1]]
    +julia> f = x -> x[1]^2 + x[2]^2
    +julia> Lie(ฯ†,f)([1, 2])
    +0
    +julia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]
    +julia> f = (t, x, v) -> t + x[1]^2 + x[2]^2
    +julia> Lie(ฯ†, f, autonomous=false, variable=true)(1, [1, 2], [2, 1])
    +10
    source
    CTFlows.Lie โ€” Method

    Lie bracket of two vector fields in the autonomous case.

    Example:

    julia> f = x -> [x[2], 2x[1]]
    +julia> g = x -> [3x[2], -x[1]]
    +julia> X = VectorField(f)
    +julia> Y = VectorField(g)
    +julia> Lie(X, Y)([1, 2])
    +[7, -14]
    source
    CTFlows.Lie โ€” Method

    Lie bracket of two vector fields in the nonautonomous case.

    Example:

    julia> f = (t, x, v) -> [t + x[2] + v, -2x[1] - v]
    +julia> g = (t, x, v) -> [t + 3x[2] + v, -x[1] - v]
    +julia> X = VectorField(f, NonAutonomous, NonFixed)
    +julia> Y = VectorField(g, NonAutonomous, NonFixed)
    +julia> Lie(X, Y)(1, [1, 2], 1)
    +[-7, 12]
    source
    CTFlows.Lift โ€” Method
    Lift(
    +    X::CTFlows.VectorField
    +) -> CTFlows.HamiltonianLift{CTFlows.VectorField{TF, TD, VD}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}
    +

    Construct the Hamiltonian lift of a VectorField.

    Arguments

    • X::VectorField: The vector field to lift. Its signature determines if it is autonomous and/or variable.

    Returns

    • A HamiltonianLift callable object representing the Hamiltonian lift of X.

    Examples

    julia> HL = Lift(VectorField(x -> [x[1]^2, x[2]^2], autonomous=true, variable=false))
    +julia> HL([1, 0], [0, 1])  # returns 0
    +
    +julia> HL2 = Lift(VectorField((t, x, v) -> [t + x[1]^2, x[2]^2 + v], autonomous=false, variable=true))
    +julia> HL2(1, [1, 0], [0, 1], 1)  # returns 1
    +
    +julia> H = Lift(x -> 2x)
    +julia> H(1, 1)  # returns 2
    +
    +julia> H2 = Lift((t, x, v) -> 2x + t - v, autonomous=false, variable=true)
    +julia> H2(1, 1, 1, 1)  # returns 2
    +
    +# Alternative syntax using symbols for autonomy and variability
    +julia> H3 = Lift((t, x, v) -> 2x + t - v, NonAutonomous, NonFixed)
    +julia> H3(1, 1, 1, 1)  # returns 2
    source
    CTFlows.Lift โ€” Method
    Lift(
    +    X::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.var"#21#22"{<:Function}
    +

    Construct the Hamiltonian lift of a function.

    Arguments

    • X::Function: The function representing the vector field.
    • autonomous::Bool=true: Whether the function is autonomous (time-independent).
    • variable::Bool=false: Whether the function depends on an additional variable argument.

    Returns

    • A callable function computing the Hamiltonian lift,

    (and variants depending on autonomous and variable).

    Details

    Depending on the autonomous and variable flags, the returned function has one of the following call signatures:

    • (x, p) if autonomous=true and variable=false
    • (x, p, v) if autonomous=true and variable=true
    • (t, x, p) if autonomous=false and variable=false
    • (t, x, p, v) if autonomous=false and variable=true

    Examples

    julia> H = Lift(x -> 2x)
    +julia> H(1, 1)  # returns 2
    +
    +julia> H2 = Lift((t, x, v) -> 2x + t - v, autonomous=false, variable=true)
    +julia> H2(1, 1, 1, 1)  # returns 2
    source
    CTFlows.Poisson โ€” Method
    Poisson(
    +    f::Function,
    +    g::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.Hamiltonian
    +

    Poisson bracket of two functions. The time and variable dependence are specified with keyword arguments.

    Returns a Hamiltonian computed from the functions promoted as Hamiltonians.

    Example

    julia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2
    +julia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]
    +julia> Poisson(f, g)([1, 2], [2, 1])     # -20
    +
    +julia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]
    +julia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]
    +julia> Poisson(f, g, autonomous=false, variable=true)(2, [1, 2], [2, 1], [4, 4])     # -76
    source
    CTFlows.Poisson โ€” Method
    Poisson(
    +    f::CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence},
    +    g::Function
    +) -> CTFlows.Hamiltonian
    +

    Poisson bracket of a Hamiltonian and a function.

    Returns a Hamiltonian representing {f, g} where f is already a Hamiltonian.

    Example

    julia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2
    +julia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]
    +julia> F = Hamiltonian(f)
    +julia> Poisson(F, g)([1, 2], [2, 1])     # -20
    +
    +julia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]
    +julia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]
    +julia> F = Hamiltonian(f, autonomous=false, variable=true)
    +julia> Poisson(F, g)(2, [1, 2], [2, 1], [4, 4])     # -76
    source
    CTFlows.Poisson โ€” Method
    Poisson(
    +    f::Function,
    +    g::CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}
    +) -> CTFlows.Hamiltonian
    +

    Poisson bracket of a function and a Hamiltonian.

    Returns a Hamiltonian representing {f, g} where g is already a Hamiltonian.

    Example

    julia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2
    +julia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]
    +julia> G = Hamiltonian(g)
    +julia> Poisson(f, G)([1, 2], [2, 1])     # -20
    +
    +julia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]
    +julia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]
    +julia> G = Hamiltonian(g, autonomous=false, variable=true)
    +julia> Poisson(f, G)(2, [1, 2], [2, 1], [4, 4])     # -76
    source
    CTFlows.Poisson โ€” Method
    Poisson(
    +    f::CTFlows.AbstractHamiltonian{CTFlows.Autonomous, V<:CTFlows.VariableDependence},
    +    g::CTFlows.AbstractHamiltonian{CTFlows.Autonomous, V<:CTFlows.VariableDependence}
    +) -> Any
    +

    Poisson bracket of two Hamiltonian functions (subtype of AbstractHamiltonian). Autonomous case.

    Returns a Hamiltonian representing the Poisson bracket {f, g} of two autonomous Hamiltonian functions f and g.

    Example

    julia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2
    +julia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]
    +julia> F = Hamiltonian(f)
    +julia> G = Hamiltonian(g)
    +julia> Poisson(f, g)([1, 2], [2, 1])     # -20
    +julia> Poisson(f, G)([1, 2], [2, 1])     # -20
    +julia> Poisson(F, g)([1, 2], [2, 1])     # -20
    source
    CTFlows.Poisson โ€” Method
    Poisson(
    +    f::CTFlows.AbstractHamiltonian{CTFlows.NonAutonomous, V<:CTFlows.VariableDependence},
    +    g::CTFlows.AbstractHamiltonian{CTFlows.NonAutonomous, V<:CTFlows.VariableDependence}
    +) -> Any
    +

    Poisson bracket of two Hamiltonian functions. Non-autonomous case.

    Returns a Hamiltonian representing {f, g} where f and g are time-dependent.

    Example

    julia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]
    +julia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]
    +julia> F = Hamiltonian(f, autonomous=false, variable=true)
    +julia> G = Hamiltonian(g, autonomous=false, variable=true)
    +julia> Poisson(F, G)(2, [1, 2], [2, 1], [4, 4])     # -76
    +julia> Poisson(f, g, NonAutonomous, NonFixed)(2, [1, 2], [2, 1], [4, 4])     # -76
    source
    CTFlows.Poisson โ€” Method
    Poisson(
    +    f::CTFlows.HamiltonianLift{T<:CTFlows.TimeDependence, V<:CTFlows.VariableDependence},
    +    g::CTFlows.HamiltonianLift{T<:CTFlows.TimeDependence, V<:CTFlows.VariableDependence}
    +)
    +

    Poisson bracket of two HamiltonianLift vector fields.

    Returns the HamiltonianLift corresponding to the Lie bracket of vector fields f.X and g.X.

    Example

    julia> f = x -> [x[1]^2 + x[2]^2, 2x[1]^2]
    +julia> g = x -> [3x[2]^2, x[2] - x[1]^2]
    +julia> F = Lift(f)
    +julia> G = Lift(g)
    +julia> Poisson(F, G)([1, 2], [2, 1])     # -64
    +
    +julia> f = (t, x, v) -> [t*v[1]*x[2]^2, 2x[1]^2 + v[2]]
    +julia> g = (t, x, v) -> [3x[2]^2 - x[1]^2, t - v[2]]
    +julia> F = Lift(f, NonAutonomous, NonFixed)
    +julia> G = Lift(g, NonAutonomous, NonFixed)
    +julia> Poisson(F, G)(2, [1, 2], [2, 1], [4, 4])     # 100
    source
    CTFlows.โˆ‚โ‚œ โ€” Method

    Partial derivative with respect to time of a function.

    Example:

    julia> โˆ‚โ‚œ((t,x) -> t*x)(0,8)
    +8
    source
    CTFlows.@Lie โ€” Macro

    Compute Lie or Poisson brackets.

    This macro provides a unified notation to define recursively nested Lie brackets (for vector fields) or Poisson brackets (for Hamiltonians).

    Syntax

    • @Lie [F, G]: computes the Lie bracket [F, G] of two vector fields.
    • @Lie [[F, G], H]: supports arbitrarily nested Lie brackets.
    • @Lie {H, K}: computes the Poisson bracket {H, K} of two Hamiltonians.
    • @Lie {{H, K}, L}: supports arbitrarily nested Poisson brackets.
    • @Lie expr autonomous = false: specifies a non-autonomous system.
    • @Lie expr variable = true: indicates presence of an auxiliary variable v.

    Keyword-like arguments can be provided to control the evaluation context for Poisson brackets with raw functions:

    • autonomous = Bool: whether the system is time-independent (default: true).
    • variable = Bool: whether the system depends on an extra variable v (default: false).

    Bracket type detection

    • Square brackets [...] denote Lie brackets between VectorField objects.
    • Curly brackets {...} denote Poisson brackets between Hamiltonian objects or between raw functions.
    • The macro automatically dispatches to Lie or Poisson depending on the input pattern.

    Return

    A callable object representing the specified Lie or Poisson bracket expression. The returned function can be evaluated like any other vector field or Hamiltonian.


    Examples

    โ–  Lie brackets with VectorField (autonomous)

    julia> F1 = VectorField(x -> [0, -x[3], x[2]])
    +julia> F2 = VectorField(x -> [x[3], 0, -x[1]])
    +julia> L = @Lie [F1, F2]
    +julia> L([1.0, 2.0, 3.0])
    +3-element Vector{Float64}:
    +  2.0
    + -1.0
    +  0.0

    โ–  Lie brackets with VectorField (non-autonomous, with auxiliary variable)

    julia> F1 = VectorField((t, x, v) -> [0, -x[3], x[2]]; autonomous=false, variable=true)
    +julia> F2 = VectorField((t, x, v) -> [x[3], 0, -x[1]]; autonomous=false, variable=true)
    +julia> L = @Lie [F1, F2]
    +julia> L(0.0, [1.0, 2.0, 3.0], 1.0)
    +3-element Vector{Float64}:
    +  2.0
    + -1.0
    +  0.0

    โ–  Poisson brackets with Hamiltonian (autonomous)

    julia> H1 = Hamiltonian((x, p) -> x[1]^2 + p[2]^2)
    +julia> H2 = Hamiltonian((x, p) -> x[2]^2 + p[1]^2)
    +julia> P = @Lie {H1, H2}
    +julia> P([1.0, 1.0], [3.0, 2.0])
    +-4.0

    โ–  Poisson brackets with Hamiltonian (non-autonomous, with variable)

    julia> H1 = Hamiltonian((t, x, p, v) -> x[1]^2 + p[2]^2 + v; autonomous=false, variable=true)
    +julia> H2 = Hamiltonian((t, x, p, v) -> x[2]^2 + p[1]^2 + v; autonomous=false, variable=true)
    +julia> P = @Lie {H1, H2}
    +julia> P(1.0, [1.0, 3.0], [4.0, 2.0], 3.0)
    +8.0

    โ–  Poisson brackets from raw functions

    julia> H1 = (x, p) -> x[1]^2 + p[2]^2
    +julia> H2 = (x, p) -> x[2]^2 + p[1]^2
    +julia> P = @Lie {H1, H2}
    +julia> P([1.0, 1.0], [3.0, 2.0])
    +-4.0

    โ–  Poisson bracket with non-autonomous raw functions

    julia> H1 = (t, x, p) -> x[1]^2 + p[2]^2 + t
    +julia> H2 = (t, x, p) -> x[2]^2 + p[1]^2 + t
    +julia> P = @Lie {H1, H2} autonomous = false
    +julia> P(3.0, [1.0, 2.0], [4.0, 1.0])
    +-8.0

    โ–  Nested brackets

    julia> F = VectorField(x -> [-x[1], x[2], x[3]])
    +julia> G = VectorField(x -> [x[3], -x[2], 0])
    +julia> H = VectorField(x -> [0, 0, -x[1]])
    +julia> nested = @Lie [[F, G], H]
    +julia> nested([1.0, 2.0, 3.0])
    +3-element Vector{Float64}:
    +  2.0
    +  0.0
    + -6.0
    julia> H1 = (x, p) -> x[2]*x[1]^2 + p[1]^2
    +julia> H2 = (x, p) -> x[1]*p[2]^2
    +julia> H3 = (x, p) -> x[1]*p[2] + x[2]*p[1]
    +julia> nested_poisson = @Lie {{H1, H2}, H3}
    +julia> nested_poisson([1.0, 2.0], [0.5, 1.0])
    +14.0

    โ–  Mixed expressions with arithmetic

    julia> F1 = VectorField(x -> [0, -x[3], x[2]])
    +julia> F2 = VectorField(x -> [x[3], 0, -x[1]])
    +julia> x = [1.0, 2.0, 3.0]
    +julia> @Lie [F1, F2](x) + 3 * [F1, F2](x)
    +3-element Vector{Float64}:
    +  8.0
    + -4.0
    +  0.0
    julia> H1 = (x, p) -> x[1]^2
    +julia> H2 = (x, p) -> p[1]^2
    +julia> H3 = (x, p) -> x[1]*p[1]
    +julia> x = [1.0, 2.0, 3.0]
    +julia> p = [3.0, 2.0, 1.0]
    +julia> @Lie {H1, H2}(x, p) + 2 * {H2, H3}(x, p)
    +24.0
    source
    diff --git a/save/docs/build/ext_default.html b/save/docs/build/ext_default.html new file mode 100644 index 00000000..48da4f82 --- /dev/null +++ b/save/docs/build/ext_default.html @@ -0,0 +1,31 @@ + +Extension Default ยท CTFlows.jl

    Extension Default

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlowsODE.__abstol โ€” Method
    __abstol() -> Float64
    +

    Default absolute tolerance for ODE solvers.

    See abstol from DifferentialEquations.

    source
    CTFlowsODE.__alg โ€” Method
    __alg(
    +
    +) -> Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), Static.False}
    +

    Default algorithm for ODE solvers.

    See alg from DifferentialEquations.

    source
    CTFlowsODE.__reltol โ€” Method
    __reltol() -> Float64
    +

    Default relative tolerance for ODE solvers.

    See reltol from DifferentialEquations.

    source
    diff --git a/save/docs/build/ext_types.html b/save/docs/build/ext_types.html new file mode 100644 index 00000000..2771e02f --- /dev/null +++ b/save/docs/build/ext_types.html @@ -0,0 +1,27 @@ + +Extension Types ยท CTFlows.jl

    Extension Types

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlowsODE.HamiltonianFlow โ€” Type
    struct HamiltonianFlow <: CTFlowsODE.AbstractFlow{Union{Real, AbstractVector{<:Real}}, Union{Real, AbstractVector{<:Real}}}

    A flow object for integrating Hamiltonian dynamics in optimal control.

    Represents the time evolution of a Hamiltonian system using the canonical form of Hamilton's equations. The struct holds the numerical integration setup and metadata for event handling.

    Fields

    • f::Function: Flow integrator function, called like f(t0, x0, p0, tf; ...).
    • rhs!::Function: Right-hand side of the ODE system, used by solvers.
    • tstops::Times: List of times at which integration should pause or apply discrete effects.
    • jumps::Vector{Tuple{Time,Costate}}: List of jump discontinuities for the costate at given times.

    Usage

    Instances of HamiltonianFlow are callable and forward arguments to the underlying flow function f.

    Example

    julia> flow = HamiltonianFlow(f, rhs!)
    +julia> xf, pf = flow(0.0, x0, p0, 1.0)
    source
    CTFlowsODE.ODEFlow โ€” Type
    struct ODEFlow <: CTFlowsODE.AbstractFlow{Any, Any}

    Generic flow object for arbitrary ODE systems with jumps and events.

    A catch-all flow for general-purpose ODE integration. Supports dynamic typing and arbitrary state structures.

    Fields

    • f::Function: Integrator function called with time span and initial conditions.
    • rhs::Function: Right-hand side for the differential equation.
    • tstops::Times: Times at which the integrator is forced to stop.
    • jumps::Vector{Tuple{Time,Any}}: User-defined jumps applied to the state during integration.

    Example

    julia> flow = ODEFlow(f, rhs)
    +julia> result = flow(0.0, u0, 1.0)
    source
    CTFlowsODE.OptimalControlFlow โ€” Type
    struct OptimalControlFlow{VD} <: CTFlowsODE.AbstractFlow{Union{Real, AbstractVector{<:Real}}, Union{Real, AbstractVector{<:Real}}}

    A flow object representing the solution of an optimal control problem.

    Supports Hamiltonian-based and classical formulations. Provides call overloads for different control settings:

    • Fixed external variables
    • Parametric (non-fixed) control problems

    Fields

    • f::Function: Main integrator that receives the RHS and other arguments.
    • rhs!::Function: ODE right-hand side.
    • tstops::Times: Times where the solver should stop (e.g., nonsmooth dynamics).
    • jumps::Vector{Tuple{Time,Costate}}: Costate jump conditions.
    • feedback_control::ControlLaw: Feedback law u(t, x, p, v).
    • ocp::Model: The optimal control problem definition.
    • kwargs_Flow::Any: Extra solver arguments.

    Call Signatures

    • F(t0, x0, p0, tf; kwargs...): Solves with fixed variable dimension.
    • F(t0, x0, p0, tf, v; kwargs...): Solves with parameter v.
    • F(tspan, x0, p0; ...): Solves and returns a full OptimalControlSolution.

    Example

    julia> flow = OptimalControlFlow(...)
    +julia> sol = flow(0.0, x0, p0, 1.0)
    +julia> opt_sol = flow((0.0, 1.0), x0, p0)
    source
    CTFlowsODE.OptimalControlFlowSolution โ€” Type
    struct OptimalControlFlowSolution

    Wraps the low-level ODE solution, control feedback law, model structure, and problem parameters.

    Fields

    • ode_sol::Any: The ODE solution (from DifferentialEquations.jl).
    • feedback_control::ControlLaw: Feedback control law u(t, x, p, v).
    • ocp::Model: The optimal control model used.
    • variable::Variable: External or design parameters of the control problem.

    Usage

    You can evaluate the flow solution like a callable ODE solution.

    Example

    julia> sol = OptimalControlFlowSolution(ode_sol, u, model, v)
    +julia> x = sol(t)
    source
    CTFlowsODE.VectorFieldFlow โ€” Type
    struct VectorFieldFlow <: CTFlowsODE.AbstractFlow{Union{Real, AbstractVector{<:Real}}, Union{Real, AbstractVector{<:Real}}}

    A flow object for integrating general vector field dynamics.

    Used for systems where the vector field is given explicitly, rather than derived from a Hamiltonian. Useful in settings like controlled systems or classical mechanics outside the Hamiltonian framework.

    Fields

    • f::Function: Flow integrator function.
    • rhs::Function: ODE right-hand side function.
    • tstops::Times: Event times (e.g., to trigger callbacks).
    • jumps::Vector{Tuple{Time,State}}: Discrete jump events on the state trajectory.

    Example

    julia> flow = VectorFieldFlow(f, rhs)
    +julia> xf = flow(0.0, x0, 1.0)
    source
    CTModels.OCP.Solution โ€” Method
    Solution(
    +    ocfs::CTFlowsODE.OptimalControlFlowSolution;
    +    kwargs...
    +) -> CTModels.OCP.Solution{TimeGridModelType, TimesModelType, StateModelType, ControlModelType, VariableModelType, ModelType, CostateModelType, Float64, DualModelType, CTModels.OCP.SolverInfos{Any, Dict{Symbol, Any}}} where {TimeGridModelType<:CTModels.OCP.AbstractTimeGridModel, TimesModelType<:CTModels.OCP.AbstractTimesModel, StateModelType<:CTModels.OCP.AbstractStateModel, ControlModelType<:CTModels.OCP.AbstractControlModel, VariableModelType<:CTModels.OCP.AbstractVariableModel, ModelType<:CTModels.OCP.AbstractModel, CostateModelType<:Function, DualModelType<:CTModels.OCP.AbstractDualModel}
    +

    Constructs an OptimalControlSolution from an OptimalControlFlowSolution.

    This evaluates the objective (Mayer and/or Lagrange costs), extracts the time-dependent state, costate, and control trajectories, and builds a full CTModels.Solution.

    Returns a CTModels.Solution ready for evaluation, reporting, or analysis.

    Keyword Arguments

    • alg: Optional solver for computing Lagrange integral, if needed.
    • Additional kwargs passed to the internal solver.

    Example

    julia> sol = Solution(optflow_solution)
    source
    diff --git a/save/docs/build/ext_utils.html b/save/docs/build/ext_utils.html new file mode 100644 index 00000000..638e4f08 --- /dev/null +++ b/save/docs/build/ext_utils.html @@ -0,0 +1,24 @@ + +Extension Utils ยท CTFlows.jl

    Extension Utils

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlowsODE.__callbacks โ€” Method
    __callbacks(
    +    callback,
    +    jumps,
    +    _rg,
    +    _t_stops_interne,
    +    tstops
    +) -> Tuple{Any, Any}
    +

    Constructs the combined callback and stopping times for flow integration.

    This internal utility assembles a CallbackSet for the ODE integrator, handling both:

    • discrete jumps in the state or costate (via VectorContinuousCallback), and
    • user-defined callbacks.

    Additionally, it merges stopping times into a sorted, unique list used for tstops.

    Arguments

    • callback: A user-defined callback (e.g. for logging or monitoring).
    • jumps: A vector of tuples (t, ฮท) representing discrete updates at time t.
    • _rg: An optional index range where the jump ฮท should be applied (e.g. only to p in (x, p)).
    • _t_stops_interne: Internal list of event times (mutable, extended in place).
    • tstops: Additional stopping times from the outer solver context.

    Returns

    • cb: A CallbackSet combining jumps and user callback.
    • t_stops_all: Sorted and deduplicated list of all stopping times.

    Example

    julia> cb, tstops = __callbacks(mycb, [(1.0, [0.0, -1.0])], 3:4, [], [2.0])
    source
    diff --git a/save/docs/build/function.html b/save/docs/build/function.html new file mode 100644 index 00000000..54f97ddf --- /dev/null +++ b/save/docs/build/function.html @@ -0,0 +1,39 @@ + +Function ยท CTFlows.jl

    Function

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.Flow โ€” Method
    Flow(
    +    dyn::Function;
    +    autonomous,
    +    variable,
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> CTFlowsODE.ODEFlow
    +

    Constructs a Flow from a user-defined dynamical system given as a Julia function.

    This high-level interface handles:

    • autonomous and non-autonomous systems,
    • presence or absence of additional variables (v),
    • selection of ODE solvers and tolerances,
    • and integrates with the CTFlows event system (e.g., jumps, callbacks).

    Arguments

    • dyn: A function defining the vector field. Its signature must match the values of autonomous and variable.
    • autonomous: Whether the dynamics are time-independent (false by default).
    • variable: Whether the dynamics depend on a control or parameter v.
    • alg, abstol, reltol, saveat, internalnorm: Solver settings passed to OrdinaryDiffEq.solve.
    • kwargs_Flow: Additional keyword arguments passed to the solver.

    Returns

    An ODEFlow object, wrapping both the full solver and its right-hand side (RHS).

    Supported Function Signatures for dyn

    Depending on the (autonomous, variable) flags:

    • (false, false): dyn(x)
    • (false, true): dyn(x, v)
    • (true, false): dyn(t, x)
    • (true, true): dyn(t, x, v)

    Example

    julia> dyn(t, x, v) = [-x[1] + v[1] * sin(t)]
    +julia> flow = CTFlows.Flow(dyn; autonomous=true, variable=true)
    +julia> xT = flow((0.0, 1.0), [1.0], [0.1])
    source
    CTFlowsODE.ode_usage โ€” Method
    ode_usage(
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm;
    +    kwargs_Flow...
    +) -> Any
    +

    Builds a solver function for general ODE problems using OrdinaryDiffEq.solve.

    This utility constructs a reusable solver function that:

    • handles optional parameters and control variables,
    • integrates with event-based CallbackSet mechanisms (including jumps),
    • supports both full solutions and one-step propagation,
    • merges solver-specific and global keyword arguments.

    Returns

    A function f that can be called in two ways:

    • f(tspan, x0, v=nothing; kwargs...) returns the full ODESolution.
    • f(t0, x0, tf, v=nothing; kwargs...) returns only the final state x(tf).

    Arguments

    • alg: The numerical integration algorithm (e.g., Tsit5()).
    • abstol: Absolute tolerance for the solver.
    • reltol: Relative tolerance for the solver.
    • saveat: Optional time steps for solution saving.
    • internalnorm: Norm function used internally for error control.
    • kwargs_Flow: Keyword arguments propagated to the solver (unless overridden).

    Example

    julia> f = ode_usage(Tsit5(), 1e-6, 1e-6, 0.1, InternalNorm())
    +julia> sol = f((0.0, 1.0), [1.0, 0.0], [0.0]; jumps=[], _t_stops_interne=[], DiffEqRHS=my_rhs)
    source
    diff --git a/save/docs/build/hamiltonian.html b/save/docs/build/hamiltonian.html new file mode 100644 index 00000000..fbf51feb --- /dev/null +++ b/save/docs/build/hamiltonian.html @@ -0,0 +1,52 @@ + +Hamiltonian ยท CTFlows.jl

    Hamiltonian

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.Flow โ€” Method
    Flow(
    +    h::CTFlows.AbstractHamiltonian;
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> CTFlowsODE.HamiltonianFlow
    +

    Constructs a Hamiltonian flow from a scalar Hamiltonian.

    This method builds a numerical integrator that simulates the evolution of a Hamiltonian system given a Hamiltonian function h(t, x, p, l) or h(x, p).

    Internally, it computes the right-hand side of Hamiltonโ€™s equations via automatic differentiation and returns a HamiltonianFlow object.

    Keyword Arguments

    • alg, abstol, reltol, saveat, internalnorm: solver options.
    • kwargs_Flow...: forwarded to the solver.

    Example

    julia> H(x, p) = dot(p, p) + dot(x, x)
    +julia> flow = CTFlows.Flow(CTFlows.Hamiltonian(H))
    +julia> xf, pf = flow(0.0, x0, p0, 1.0)
    source
    CTFlows.Flow โ€” Method
    Flow(
    +    hv::CTFlows.HamiltonianVectorField;
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> CTFlowsODE.HamiltonianFlow
    +

    Constructs a Hamiltonian flow from a precomputed Hamiltonian vector field.

    This method assumes you already provide the Hamiltonian vector field (dx/dt, dp/dt) instead of deriving it from a scalar Hamiltonian.

    Returns a HamiltonianFlow object that integrates the given system.

    Keyword Arguments

    • alg, abstol, reltol, saveat, internalnorm: solver options.
    • kwargs_Flow...: forwarded to the solver.

    Example

    julia> hv(t, x, p, l) = (โˆ‡โ‚šH, -โˆ‡โ‚“H)
    +julia> flow = CTFlows.Flow(CTFlows.HamiltonianVectorField(hv))
    +julia> xf, pf = flow(0.0, x0, p0, 1.0, l)
    source
    CTFlowsODE.hamiltonian_usage โ€” Method
    hamiltonian_usage(
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm;
    +    kwargs_Flow...
    +) -> Any
    +

    Constructs a solver function for Hamiltonian systems, with configurable solver options.

    Returns a callable object that integrates Hamilton's equations from an initial state (x0, p0) over a time span tspan = (t0, tf), with optional external parameters.

    The returned function has two methods:

    • f(tspan, x0, p0, v=default_variable; kwargs...) โ†’ returns the full trajectory (solution object).
    • f(t0, x0, p0, tf, v=default_variable; kwargs...) โ†’ returns only the final (x, p) state.

    Internally, it uses OrdinaryDiffEq.solve and supports events and callbacks.

    Arguments

    • alg: integration algorithm (e.g. Tsit5()).
    • abstol, reltol: absolute and relative tolerances.
    • saveat: time points for saving.
    • internalnorm: norm used for adaptive integration.
    • kwargs_Flow...: additional keyword arguments for the solver.

    Example

    julia> flowfun = hamiltonian_usage(Tsit5(), 1e-8, 1e-8, 0.1, norm)
    +julia> xf, pf = flowfun(0.0, x0, p0, 1.0)
    source
    CTFlowsODE.rhs โ€” Method
    rhs(
    +    h::CTFlows.AbstractHamiltonian
    +) -> CTFlowsODE.var"#rhs!#rhs##0"{<:CTFlows.AbstractHamiltonian{TD, VD}} where {TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}
    +

    Constructs the right-hand side of Hamilton's equations from a scalar Hamiltonian function.

    Given a Hamiltonian h(t, x, p, l) (or h(x, p) in the autonomous case), returns an in-place function rhs!(dz, z, v, t) suitable for numerical integration.

    This function computes the canonical Hamiltonian vector field using automatic differentiation:

    dz[1:n]     =  โˆ‚H/โˆ‚p
    +dz[n+1:2n]  = -โˆ‚H/โˆ‚x

    Arguments

    • h: a subtype of CTFlows.AbstractHamiltonian defining the scalar Hamiltonian.

    Returns

    • rhs!: a function for use in an ODE solver.
    source
    diff --git a/save/docs/build/index.html b/save/docs/build/index.html new file mode 100644 index 00000000..5f7e6958 --- /dev/null +++ b/save/docs/build/index.html @@ -0,0 +1,7 @@ + +Introduction ยท CTFlows.jl

    CTFlows.jl

    The CTFlows.jl package is part of the control-toolbox ecosystem.

    The root package is OptimalControl.jl which aims to provide tools to model and solve optimal control problems with ordinary differential equations by direct and indirect methods, both on CPU and GPU.

    API Documentation

    diff --git a/save/docs/build/objects.inv b/save/docs/build/objects.inv new file mode 100644 index 00000000..c6cdb760 Binary files /dev/null and b/save/docs/build/objects.inv differ diff --git a/save/docs/build/optimal_control_problem.html b/save/docs/build/optimal_control_problem.html new file mode 100644 index 00000000..d80e218f --- /dev/null +++ b/save/docs/build/optimal_control_problem.html @@ -0,0 +1,78 @@ + +Optimal Control Problem ยท CTFlows.jl

    Optimal Control Problem

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.Flow โ€” Method
    Flow(
    +    ocp::CTModels.OCP.Model,
    +    u::CTFlows.ControlLaw;
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
    +

    Construct a flow for an optimal control problem using a given control law.

    This method builds the Hamiltonian system associated with the optimal control problem (ocp) and integrates the corresponding stateโ€“costate dynamics using the specified control law u.

    Arguments

    • ocp::CTModels.Model: An optimal control problem defined using CTModels.
    • u::CTFlows.ControlLaw: A feedback control law generated by ControlLaw(...) or similar.
    • alg: Integration algorithm (default inferred).
    • abstol: Absolute tolerance for the ODE solver.
    • reltol: Relative tolerance for the ODE solver.
    • saveat: Time points at which to save the solution.
    • internalnorm: Optional norm function used by the integrator.
    • kwargs_Flow: Additional keyword arguments passed to the solver.

    Returns

    A flow object f such that:

    • f(t0, x0, p0, tf) integrates the state and costate from t0 to tf.
    • f((t0, tf), x0, p0) returns the full trajectory over the interval.

    Example

    julia> u = (x, p) -> p
    +julia> f = Flow(ocp, ControlLaw(u))
    source
    CTFlows.Flow โ€” Method
    Flow(
    +    ocp::CTModels.OCP.Model,
    +    u::Function,
    +    g::Function,
    +    ฮผ::Function;
    +    autonomous,
    +    variable,
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
    +

    Construct a flow from a raw feedback control, constraint, and multiplier.

    This version is for defining flows directly from user functions without wrapping them into ControlLaw, Constraint, or Multiplier types. Automatically wraps and adapts them based on time dependence.

    Arguments

    • ocp::CTModels.Model: The optimal control problem.
    • u::Function: Control law.
    • g::Function: Constraint.
    • ฮผ::Function: Multiplier.
    • autonomous::Bool: Whether the system is autonomous.
    • variable::Bool: Whether time is a free variable.
    • alg, abstol, reltol, saveat, internalnorm: Solver parameters.
    • kwargs_Flow: Additional options.

    Returns

    A Flow object ready for trajectory integration.

    source
    CTFlows.Flow โ€” Method
    Flow(
    +    ocp::CTModels.OCP.Model,
    +    u::Function;
    +    autonomous,
    +    variable,
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
    +

    Construct a flow for an optimal control problem using a control function in feedback form.

    This method constructs the Hamiltonian and integrates the associated stateโ€“costate dynamics using a raw function u. It automatically wraps u as a control law.

    Arguments

    • ocp::CTModels.Model: The optimal control problem.
    • u::Function: A feedback control function:
      • If ocp is autonomous: u(x, p)
      • If non-autonomous: u(t, x, p)
    • autonomous::Bool: Whether the control law depends on time.
    • variable::Bool: Whether the OCP involves variable time (e.g., free final time).
    • alg, abstol, reltol, saveat, internalnorm: ODE solver parameters.
    • kwargs_Flow: Additional options.

    Returns

    A Flow object compatible with function call interfaces for state propagation.

    Example

    julia> u = (t, x, p) -> t + p
    +julia> f = Flow(ocp, u)
    source
    CTFlows.Flow โ€” Method
    Flow(
    +    ocp::CTModels.OCP.Model,
    +    u::Union{CTFlows.ControlLaw{<:Function, T, V}, CTFlows.FeedbackControl{<:Function, T, V}},
    +    g::Union{CTFlows.MixedConstraint{<:Function, T, V}, CTFlows.StateConstraint{<:Function, T, V}},
    +    ฮผ::CTFlows.Multiplier{<:Function, T, V};
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
    +

    Construct a flow for an optimal control problem with control and constraint multipliers in feedback form.

    This variant constructs a Hamiltonian system incorporating both the control law and a multiplier law (e.g., for enforcing state or mixed constraints). All inputs must be consistent in time dependence.

    Arguments

    • ocp::CTModels.Model: The optimal control problem.
    • u::ControlLaw or FeedbackControl: Feedback control.
    • g::StateConstraint or MixedConstraint: Constraint function.
    • ฮผ::Multiplier: Multiplier function.
    • alg, abstol, reltol, saveat, internalnorm: Solver settings.
    • kwargs_Flow: Additional options.

    Returns

    A Flow object that integrates the constrained Hamiltonian dynamics.

    Example

    julia> f = Flow(ocp, (x, p) -> p[1], (x, u) -> x[1] - 1, (x, p) -> x[1]+p[1])

    For non-autonomous cases:

    julia> f = Flow(ocp, (t, x, p) -> t + p, (t, x, u) -> x - 1, (t, x, p) -> x+p)
    Warning

    All input functions must match the autonomous/non-autonomous nature of the problem.

    source
    CTFlowsODE.__ocp_Flow โ€” Method
    __ocp_Flow(
    +    ocp::CTModels.OCP.Model,
    +    h::CTFlows.Hamiltonian,
    +    u::CTFlows.ControlLaw,
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm;
    +    kwargs_Flow...
    +) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}
    +

    Internal helper: builds the OptimalControlFlow object from a Hamiltonian and control law.

    This function assembles the RHS, constructs the integrator, and packages the flow object.

    Arguments

    • ocp: The original optimal control problem.
    • h: A Hamiltonian structure.
    • u: A control law.
    • alg, abstol, reltol, saveat, internalnorm: Integration parameters.
    • kwargs_Flow: Additional parameters.

    Returns

    An OptimalControlFlow object, callable as a function for integration.

    Note

    This function is internal and intended for constructing flows behind the scenes.

    source
    diff --git a/save/docs/build/optimal_control_problem_utils.html b/save/docs/build/optimal_control_problem_utils.html new file mode 100644 index 00000000..199cd05d --- /dev/null +++ b/save/docs/build/optimal_control_problem_utils.html @@ -0,0 +1,108 @@ + +Optimal Control Problem Utils ยท CTFlows.jl

    Optimal Control Problem Utils

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::Function,
    +    g,
    +    ฮผ;
    +    autonomous,
    +    variable
    +)
    +

    Overload for control law as a raw function with autonomous and variable flags.

    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::Function;
    +    autonomous,
    +    variable
    +)
    +

    Helper method to construct the Hamiltonian when control is given as a plain function.

    The function is wrapped in a ControlLaw, and the flags autonomous and variable define its behavior type.

    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::CTFlows.ControlLaw{<:Function, T, V},
    +    g::CTFlows.MixedConstraint{<:Function, T, V},
    +    ฮผ::CTFlows.Multiplier{<:Function, T, V}
    +) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var"#H#makeH##3"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var"#H#makeH##4"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}
    +

    Construct the Hamiltonian for a constrained optimal control problem.

    This function supports multiple input types for the control law (u), path constraints (g), and multipliers (ฮผ), automatically adapting to autonomous/non-autonomous systems and fixed/non-fixed parameters.

    Supported input types

    • u can be:

      • a raw function,
      • a ControlLaw object,
      • or a FeedbackControl object.
    • g can be:

      • a plain constraint function,
      • a MixedConstraint,
      • or a StateConstraint.
    • ฮผ can be:

      • a function,
      • or a Multiplier object.

    The function normalizes these inputs to the appropriate types internally using multiple dispatch and pattern matching.

    Arguments

    • ocp::CTModels.Model: The continuous-time optimal control problem.
    • u: Control law, flexible input type as described.
    • g: Path constraint, flexible input type as described.
    • ฮผ: Multiplier associated with the constraints.
    • autonomous::Bool (optional keyword): Specifies if the system is autonomous.
    • variable::Bool (optional keyword): Specifies if the system parameters are variable.

    Returns

    • (H, u): Tuple containing the Hamiltonian object H and the processed control law u.

    Examples

    # Using a raw function control law with autonomous system and fixed parameters
    +H, u_processed = __create_hamiltonian(ocp, u_function, g_function, ฮผ_function; autonomous=true, variable=false)
    +
    +# Using a FeedbackControl control law
    +H, u_processed = __create_hamiltonian(ocp, feedback_control, g_constraint, ฮผ_multiplier)
    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::CTFlows.ControlLaw{<:Function, T, V},
    +    g::CTFlows.MixedConstraint{<:Function, T, V},
    +    ฮผ::Function
    +) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var"#H#makeH##3"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var"#H#makeH##4"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}
    +

    Overload that wraps multiplier functions into Multiplier objects.

    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::CTFlows.ControlLaw{<:Function, T, V},
    +    g_::CTFlows.StateConstraint{<:Function, T, V},
    +    ฮผ
    +) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var"#H#makeH##3"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var"#H#makeH##4"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}
    +

    Overload that converts StateConstraint objects into MixedConstraint with appropriate signature adaptation.

    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::CTFlows.ControlLaw{<:Function, T, V},
    +    g::Function,
    +    ฮผ
    +) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var"#H#makeH##3"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var"#H#makeH##4"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}
    +

    Overload that wraps plain constraint functions into MixedConstraint objects.

    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u::CTFlows.ControlLaw{<:Function, T, V}
    +) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var"#H#makeH##2"{f, u, fโฐ, pโฐ, s}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s}, CTFlows.Hamiltonian{CTFlows.var"#makeH##0#makeH##1"{f, u}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u}}, CTFlows.ControlLaw}
    +

    Construct and return the Hamiltonian for the given model and control law.

    The Hamiltonian is built using model dynamics (and possibly a running cost) and returned as a callable function.

    Returns a tuple (H, u) where H is the Hamiltonian function and u is the control law.

    source
    CTFlows.__create_hamiltonian โ€” Method
    __create_hamiltonian(
    +    ocp::CTModels.OCP.Model,
    +    u_::CTFlows.FeedbackControl{<:Function, T, V},
    +    g,
    +    ฮผ
    +) -> Any
    +

    Overload for feedback control laws that adapts the signature based on autonomy and variability.

    source
    CTFlows.__dynamics โ€” Method
    __dynamics(
    +    ocp::CTModels.OCP.Model
    +) -> CTFlows.Dynamics{CTFlows.var"#__dynamics##0#__dynamics##1"{ocp, n}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {ocp, n}
    +

    Return a Dynamics object built from the model's right-hand side function.

    The returned function computes the state derivative dx/dt = f(t, x, u, v), wrapped to return either a scalar or vector depending on the model's state dimension.

    source
    CTFlows.__get_data_for_ocp_flow โ€” Method
    __get_data_for_ocp_flow(
    +    ocp::CTModels.OCP.Model
    +) -> Tuple{CTFlows.Dynamics{CTFlows.var"#__dynamics##0#__dynamics##1"{ocp, n}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {ocp, n}, Union{Nothing, CTFlows.Lagrange{<:Function, CTFlows.NonAutonomous, CTFlows.NonFixed}}, Int64, Float64}
    +

    Return internal components needed to construct the OCP Hamiltonian.

    Returns a tuple (f, fโฐ, pโฐ, s) where:

    • f : system dynamics (Dynamics)
    • fโฐ : optional Lagrange integrand (Lagrange or nothing)
    • pโฐ : constant multiplier for cost (typically -1)
    • s : sign for minimization/maximization (+1 or -1)
    source
    CTFlows.__is_min โ€” Method
    __is_min(ocp::CTModels.OCP.Model) -> Bool
    +

    Return true if the given model defines a minimization problem, false otherwise.

    source
    CTFlows.__lagrange โ€” Method
    __lagrange(
    +    ocp::CTModels.OCP.Model
    +) -> Union{Nothing, CTFlows.Lagrange{<:Function, CTFlows.NonAutonomous, CTFlows.NonFixed}}
    +

    Return a Lagrange object if the model includes an integrand cost; otherwise, return nothing.

    The resulting function can be used to compute the running cost of the optimal control problem.

    source
    CTFlows.__mayer โ€” Method
    __mayer(
    +    ocp::CTModels.OCP.Model
    +) -> Union{Nothing, CTFlows.Mayer{<:Function, CTFlows.NonFixed}}
    +

    Return a Mayer object if the model includes a terminal cost; otherwise, return nothing.

    The resulting function can be used to compute the final cost in the objective.

    source
    CTFlows.makeH โ€” Method
    makeH(
    +    f::CTFlows.Dynamics,
    +    u::CTFlows.ControlLaw,
    +    fโฐ::CTFlows.Lagrange,
    +    pโฐ::Real,
    +    s::Real,
    +    g::CTFlows.MixedConstraint,
    +    ฮผ::CTFlows.Multiplier
    +) -> CTFlows.var"#H#makeH##4"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}, CTFlows.Lagrange{TF2, TD2, VD2}, var"#s179", var"#s1791", CTFlows.MixedConstraint{TF3, TD3, VD3}, CTFlows.Multiplier{TF4, TD4, VD4}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence, TF2<:Function, TD2<:CTFlows.TimeDependence, VD2<:CTFlows.VariableDependence, var"#s179"<:Real, var"#s1791"<:Real, TF3<:Function, TD3<:CTFlows.TimeDependence, VD3<:CTFlows.VariableDependence, TF4<:Function, TD4<:CTFlows.TimeDependence, VD4<:CTFlows.VariableDependence}
    +

    Construct the Hamiltonian:

    H(t, x, p) = p โ‹… f(t, x, u(t, x, p)) + s pโฐ fโฐ(t, x, u(t, x, p)) + ฮผ(t, x, p) โ‹… g(t, x, u(t, x, p))

    Combines integrand cost and path constraints.

    source
    CTFlows.makeH โ€” Method
    makeH(
    +    f::CTFlows.Dynamics,
    +    u::CTFlows.ControlLaw,
    +    fโฐ::CTFlows.Lagrange,
    +    pโฐ::Real,
    +    s::Real
    +) -> CTFlows.var"#H#makeH##2"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}, CTFlows.Lagrange{TF2, TD2, VD2}, <:Real, <:Real} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence, TF2<:Function, TD2<:CTFlows.TimeDependence, VD2<:CTFlows.VariableDependence}
    +

    Construct the Hamiltonian:

    H(t, x, p) = p โ‹… f(t, x, u(t, x, p)) + s pโฐ fโฐ(t, x, u(t, x, p))

    Includes a Lagrange integrand scaled by pโฐ and sign s.

    source
    CTFlows.makeH โ€” Method
    makeH(
    +    f::CTFlows.Dynamics,
    +    u::CTFlows.ControlLaw,
    +    g::CTFlows.MixedConstraint,
    +    ฮผ::CTFlows.Multiplier
    +) -> CTFlows.var"#H#makeH##3"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}, CTFlows.MixedConstraint{TF2, TD2, VD2}, CTFlows.Multiplier{TF3, TD3, VD3}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence, TF2<:Function, TD2<:CTFlows.TimeDependence, VD2<:CTFlows.VariableDependence, TF3<:Function, TD3<:CTFlows.TimeDependence, VD3<:CTFlows.VariableDependence}
    +

    Construct the Hamiltonian:

    H(t, x, p) = p โ‹… f(t, x, u(t, x, p)) + ฮผ(t, x, p) โ‹… g(t, x, u(t, x, p))

    Includes state-control constraints and associated multipliers.

    source
    CTFlows.makeH โ€” Method
    makeH(
    +    f::CTFlows.Dynamics,
    +    u::CTFlows.ControlLaw
    +) -> CTFlows.var"#makeH##0#makeH##1"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence}
    +

    Construct the Hamiltonian:

    H(t, x, p) = p โ‹… f(t, x, u(t, x, p))

    The function returns a callable H(t, x, p, v) where v is an optional additional parameter.

    source
    diff --git a/save/docs/build/search_index.js b/save/docs/build/search_index.js new file mode 100644 index 00000000..99532cb7 --- /dev/null +++ b/save/docs/build/search_index.js @@ -0,0 +1,3 @@ +var documenterSearchIndex = {"docs": +[{"location":"types.html#Types","page":"Types","title":"Types","text":"","category":"section"},{"location":"types.html#Index","page":"Types","title":"Index","text":"Pages = [\"types.md\"]\nModules = [CTFlows]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"types.html#Documentation","page":"Types","title":"Documentation","text":"","category":"section"},{"location":"types.html#CTFlows.Control","page":"Types","title":"CTFlows.Control","text":"Alias for control input variables (scalar or vector).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Costate","page":"Types","title":"CTFlows.Costate","text":"Alias for the costate (adjoint) variable (scalar or vector).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.DCostate","page":"Types","title":"CTFlows.DCostate","text":"Alias for derivatives of the costate (scalar or vector).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.DState","page":"Types","title":"CTFlows.DState","text":"Alias for derivatives of the state (scalar or vector).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.State","page":"Types","title":"CTFlows.State","text":"Alias for the system state (scalar or vector).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Variable","page":"Types","title":"CTFlows.Variable","text":"Alias for generic variables (scalar or vector).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.ctVector","page":"Types","title":"CTFlows.ctVector","text":"Scalar or vector type used in continuous-time models (scalar or vector-valued).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.AbstractHamiltonian","page":"Types","title":"CTFlows.AbstractHamiltonian","text":"abstract type AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.AbstractVectorField","page":"Types","title":"CTFlows.AbstractVectorField","text":"abstract type AbstractVectorField{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Autonomous","page":"Types","title":"CTFlows.Autonomous","text":"abstract type Autonomous <: CTFlows.TimeDependence\n\nIndicates the function is autonomous: it does not explicitly depend on time t.\n\nFor example, dynamics of the form f(x, u, p).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.ControlLaw","page":"Types","title":"CTFlows.ControlLaw","text":"struct ControlLaw{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nRepresents a generic open-loop or closed-loop control law.\n\nExample\n\njulia> f(t, x) = -x * exp(-t)\njulia> u = ControlLaw{typeof(f), NonAutonomous, Fixed}(f)\njulia> u(1.0, [2.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.ControlLaw-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.ControlLaw","text":"ControlLaw(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.ControlLaw\n\n\nConstruct a ControlLaw specifying time and variable dependence types.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.ControlLaw-Tuple{Function}","page":"Types","title":"CTFlows.ControlLaw","text":"ControlLaw(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.ControlLaw\n\n\nConstruct a ControlLaw wrapping the function f.\n\nArguments\n\nf::Function: The function defining the control law.\nautonomous::Bool: Whether the system is autonomous.\nvariable::Bool: Whether the function depends on additional variables.\n\nReturns\n\nA ControlLaw instance parameterized accordingly.\n\nExample\n\njulia> cl = ControlLaw((x, p) -> -p)\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Dynamics","page":"Types","title":"CTFlows.Dynamics","text":"struct Dynamics{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nRepresents the system dynamics dx/dt = f(...).\n\nFields\n\nf: a callable of the form:\nf(x, u)\nf(t, x, u)\nf(x, u, v)\nf(t, x, u, v)\n\nExample\n\njulia> f(x, u) = x + u\njulia> dyn = Dynamics{typeof(f), Autonomous, Fixed}(f)\njulia> dyn([1.0], [2.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Dynamics-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.Dynamics","text":"Dynamics(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.Dynamics\n\n\nCreate a Dynamics object with explicit time and variable dependence.\n\nArguments\n\nf::Function: The dynamics function.\nTD: Type indicating time dependence.\nVD: Type indicating variable dependence.\n\nReturns\n\nA Dynamics{typeof(f),TD,VD} object.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Dynamics-Tuple{Function}","page":"Types","title":"CTFlows.Dynamics","text":"Dynamics(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.Dynamics\n\n\nCreate a Dynamics object representing system dynamics.\n\nArguments\n\nf::Function: The dynamics function.\nautonomous::Bool (optional): Whether the dynamics are autonomous (time-independent). Defaults to __autonomous().\nvariable::Bool (optional): Whether the dynamics depend on variables (non-fixed). Defaults to __variable().\n\nReturns\n\nA Dynamics{typeof(f),TD,VD} object.\n\nDetails\n\nThe Dynamics object can be called with various signatures depending on time and variable dependence.\n\nExamples\n\njulia> f(x, u) = [x[2], -x[1] + u[1]]\njulia> dyn = Dynamics(f, autonomous=true, variable=false)\njulia> dyn([1.0, 0.0], [0.0]) # returns [0.0, -1.0]\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.FeedbackControl","page":"Types","title":"CTFlows.FeedbackControl","text":"struct FeedbackControl{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nRepresents a feedback control law: u = f(x) or u = f(t, x).\n\nExample\n\njulia> f(x) = -x\njulia> u = FeedbackControl{typeof(f), Autonomous, Fixed}(f)\njulia> u([1.0, -1.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.FeedbackControl-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.FeedbackControl","text":"FeedbackControl(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.FeedbackControl\n\n\nConstruct a FeedbackControl specifying time and variable dependence types.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.FeedbackControl-Tuple{Function}","page":"Types","title":"CTFlows.FeedbackControl","text":"FeedbackControl(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.FeedbackControl\n\n\nConstruct a FeedbackControl wrapping the function f.\n\nArguments\n\nf::Function: The function defining the feedback control.\nautonomous::Bool: Whether the system is autonomous.\nvariable::Bool: Whether the function depends on additional variables.\n\nReturns\n\nA FeedbackControl instance parameterized accordingly.\n\nExample\n\njulia> fb = FeedbackControl(x -> -x)\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Fixed","page":"Types","title":"CTFlows.Fixed","text":"abstract type Fixed <: CTFlows.VariableDependence\n\nIndicates the function has fixed standard arguments only.\n\nFor example, functions of the form f(t, x, p) without any extra variable argument.\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Hamiltonian","page":"Types","title":"CTFlows.Hamiltonian","text":"struct Hamiltonian{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nEncodes the Hamiltonian function H = โŸจp, fโŸฉ + L in optimal control.\n\nFields\n\nf: a callable of the form:\nf(x, p)\nf(t, x, p)\nf(x, p, v)\nf(t, x, p, v)\n\nType Parameters\n\nTD: Autonomous or NonAutonomous\nVD: Fixed or NonFixed\n\nExample\n\njulia> Hf(x, p) = dot(p, [x[2], -x[1]])\njulia> H = Hamiltonian{typeof(Hf), Autonomous, Fixed}(Hf)\njulia> H([1.0, 0.0], [1.0, 1.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Hamiltonian-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.Hamiltonian","text":"Hamiltonian(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.Hamiltonian\n\n\nConstruct a Hamiltonian function wrapper with explicit time and variable dependence types.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Hamiltonian-Tuple{Function}","page":"Types","title":"CTFlows.Hamiltonian","text":"Hamiltonian(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.Hamiltonian\n\n\nConstruct a Hamiltonian function wrapper.\n\nf: a function representing the Hamiltonian.\nautonomous: whether f is autonomous (default via __autonomous()).\nvariable: whether f depends on an extra variable argument (default via __variable()).\n\nReturns a Hamiltonian{TF, TD, VD} callable struct.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.HamiltonianLift","page":"Types","title":"CTFlows.HamiltonianLift","text":"struct HamiltonianLift{TV<:CTFlows.VectorField, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nLifts a vector field X into a Hamiltonian function using the canonical symplectic structure.\n\nThis is useful to convert a vector field into a Hamiltonian via the identity: H(x, p) = โŸจp, X(x)โŸฉ.\n\nConstructor\n\nUse HamiltonianLift(X::VectorField) where X is a VectorField{...}.\n\nExample\n\nf(x) = [x[2], -x[1]]\njulia> X = VectorField{typeof(f), Autonomous, Fixed}(f)\njulia> H = HamiltonianLift(X)\njulia> H([1.0, 0.0], [0.5, 0.5])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.HamiltonianLift-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.HamiltonianLift","text":"HamiltonianLift(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.HamiltonianLift{CTFlows.VectorField{TF, TD, VD}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\n\nConstruct a HamiltonianLift with explicit time and variable dependence types.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.HamiltonianLift-Tuple{Function}","page":"Types","title":"CTFlows.HamiltonianLift","text":"HamiltonianLift(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.HamiltonianLift{CTFlows.VectorField{TF, TD, VD}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\n\nConstruct a HamiltonianLift from a vector field function.\n\nf: function defining the vector field.\nautonomous: whether f is autonomous.\nvariable: whether f depends on an extra variable argument.\n\nReturns a HamiltonianLift wrapping the vector field.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.HamiltonianVectorField","page":"Types","title":"CTFlows.HamiltonianVectorField","text":"struct HamiltonianVectorField{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractVectorField{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nRepresents the Hamiltonian vector field associated to a Hamiltonian function, typically defined as (โˆ‚H/โˆ‚p, -โˆ‚H/โˆ‚x).\n\nFields\n\nf: a callable implementing the Hamiltonian vector field.\n\nExample\n\njulia> f(x, p) = [p[2], -p[1], -x[1], -x[2]]\njulia> XH = HamiltonianVectorField{typeof(f), Autonomous, Fixed}(f)\njulia> XH([1.0, 0.0], [0.5, 0.5])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.HamiltonianVectorField-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.HamiltonianVectorField","text":"HamiltonianVectorField(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.HamiltonianVectorField\n\n\nConstruct a Hamiltonian vector field with explicit time and variable dependence.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.HamiltonianVectorField-Tuple{Function}","page":"Types","title":"CTFlows.HamiltonianVectorField","text":"HamiltonianVectorField(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.HamiltonianVectorField\n\n\nConstruct a Hamiltonian vector field from a function f.\n\nautonomous: whether f is autonomous.\nvariable: whether f depends on an extra variable argument.\n\nReturns a HamiltonianVectorField{TF, TD, VD} callable struct.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Lagrange","page":"Types","title":"CTFlows.Lagrange","text":"struct Lagrange{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nEncodes the integrand L(t, x, u, ...) of the cost functional in Bolza optimal control problems.\n\nFields\n\nf: a callable such as:\nf(x, u)\nf(t, x, u)\nf(x, u, v)\nf(t, x, u, v)\n\nExample\n\njulia> L(x, u) = dot(x, x) + dot(u, u)\njulia> lag = Lagrange{typeof(L), Autonomous, Fixed}(L)\njulia> lag([1.0, 2.0], [0.5, 0.5])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Lagrange-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.Lagrange","text":"Lagrange(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.Lagrange\n\n\nCreate a Lagrange object with explicit time and variable dependence.\n\nArguments\n\nf::Function: The Lagrangian function.\nTD: Type indicating time dependence.\nVD: Type indicating variable dependence.\n\nReturns\n\nA Lagrange{typeof(f),TD,VD} object.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Lagrange-Tuple{Function}","page":"Types","title":"CTFlows.Lagrange","text":"Lagrange(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.Lagrange\n\n\nCreate a Lagrange object representing a Lagrangian cost function.\n\nArguments\n\nf::Function: The Lagrangian function.\nautonomous::Bool (optional): Whether f is autonomous (time-independent). Defaults to __autonomous().\nvariable::Bool (optional): Whether f depends on variables (non-fixed). Defaults to __variable().\n\nReturns\n\nA Lagrange{typeof(f),TD,VD} object.\n\nDetails\n\nThe Lagrange object can be called with different argument signatures depending on the time and variable dependence.\n\nExamples\n\njulia> f(x, u) = sum(abs2, x) + sum(abs2, u)\njulia> lag = Lagrange(f, autonomous=true, variable=false)\njulia> lag([1.0, 2.0], [0.5, 0.5]) # returns 5.25\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Mayer","page":"Types","title":"CTFlows.Mayer","text":"struct Mayer{TF<:Function, VD<:CTFlows.VariableDependence}\n\nEncodes the Mayer cost function in optimal control problems.\n\nThis terminal cost term is usually of the form ฯ†(x(tf)) or ฯ†(t, x(tf), v), depending on whether it's autonomous and/or variable-dependent.\n\nFields\n\nf: a callable of the form:\nf(x)\nf(x, v)\nf(t, x)\nf(t, x, v) depending on time and variable dependency.\n\nExample\n\njulia> ฯ†(x) = norm(x)^2\njulia> m = Mayer{typeof(ฯ†), Fixed}(ฯ†)\njulia> m([1.0, 2.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Mayer-Tuple{Function, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.Mayer","text":"Mayer(\n f::Function,\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.Mayer\n\n\nConstruct a Mayer cost functional wrapper with explicit variable dependence type VD.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Mayer-Tuple{Function}","page":"Types","title":"CTFlows.Mayer","text":"Mayer(\n f::Function;\n variable\n) -> Union{CTFlows.Mayer{<:Function, CTFlows.Fixed}, CTFlows.Mayer{<:Function, CTFlows.NonFixed}}\n\n\nConstruct a Mayer cost functional wrapper.\n\nf: a function representing the Mayer cost.\nvariable: whether the function depends on an extra variable argument (default via __variable()).\n\nReturns a Mayer{TF, VD} callable struct where:\n\nTF is the function type\nVD is either Fixed or NonFixed depending on variable.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.MixedConstraint","page":"Types","title":"CTFlows.MixedConstraint","text":"struct MixedConstraint{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nEncodes a constraint on both state and control: g(x, u) = 0 or g(t, x, u) = 0.\n\nExample\n\njulia> g(x, u) = x[1] + u[1] - 1\njulia> mc = MixedConstraint{typeof(g), Autonomous, Fixed}(g)\njulia> mc([0.3], [0.7])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.MixedConstraint-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.MixedConstraint","text":"MixedConstraint(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.MixedConstraint\n\n\nConstruct a MixedConstraint specifying the time and variable dependence types explicitly.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.MixedConstraint-Tuple{Function}","page":"Types","title":"CTFlows.MixedConstraint","text":"MixedConstraint(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.MixedConstraint\n\n\nConstruct a MixedConstraint object wrapping the function f.\n\nArguments\n\nf::Function: The function defining the mixed constraint.\nautonomous::Bool: Whether the system is autonomous.\nvariable::Bool: Whether the function depends on additional variables.\n\nReturns\n\nA MixedConstraint instance parameterized by the type of f and time/variable dependence.\n\nExample\n\njulia> mc = MixedConstraint((x, u) -> x + u)\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Multiplier","page":"Types","title":"CTFlows.Multiplier","text":"struct Multiplier{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nEncodes a Lagrange multiplier associated with a constraint.\n\nExample\n\njulia> ฮป(t) = [sin(t), cos(t)]\njulia> ฮผ = Multiplier{typeof(ฮป), NonAutonomous, Fixed}(ฮป)\njulia> ฮผ(ฯ€ / 2)\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Multiplier-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.Multiplier","text":"Multiplier(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.Multiplier\n\n\nConstruct a Multiplier specifying time and variable dependence types.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Multiplier-Tuple{Function}","page":"Types","title":"CTFlows.Multiplier","text":"Multiplier(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.Multiplier\n\n\nConstruct a Multiplier wrapping the function f.\n\nArguments\n\nf::Function: The function defining the multiplier.\nautonomous::Bool: Whether the system is autonomous.\nvariable::Bool: Whether the function depends on additional variables.\n\nReturns\n\nA Multiplier instance parameterized accordingly.\n\nExample\n\njulia> m = Multiplier((x, p) -> p .* x)\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.NonAutonomous","page":"Types","title":"CTFlows.NonAutonomous","text":"abstract type NonAutonomous <: CTFlows.TimeDependence\n\nIndicates the function is non-autonomous: it explicitly depends on time t.\n\nFor example, dynamics of the form f(t, x, u, p).\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.NonFixed","page":"Types","title":"CTFlows.NonFixed","text":"abstract type NonFixed <: CTFlows.VariableDependence\n\nIndicates the function has an additional variable argument v.\n\nFor example, functions of the form f(t, x, p, v) where v is a multiplier or auxiliary parameter.\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.StateConstraint","page":"Types","title":"CTFlows.StateConstraint","text":"struct StateConstraint{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nEncodes a pure state constraint g(x) = 0 or g(t, x) = 0.\n\nFields\n\nf: a callable depending on time or not, with or without variable dependency.\n\nExample\n\njulia> g(x) = x[1]^2 + x[2]^2 - 1\njulia> c = StateConstraint{typeof(g), Autonomous, Fixed}(g)\njulia> c([1.0, 0.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.StateConstraint-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.StateConstraint","text":"StateConstraint(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.StateConstraint\n\n\nConstruct a StateConstraint specifying the time and variable dependence types explicitly.\n\nArguments\n\nf::Function: The function defining the state constraint.\nTD::Type: The time dependence type (Autonomous or NonAutonomous).\nVD::Type: The variable dependence type (Fixed or NonFixed).\n\nReturns\n\nA StateConstraint instance parameterized accordingly.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.StateConstraint-Tuple{Function}","page":"Types","title":"CTFlows.StateConstraint","text":"StateConstraint(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.StateConstraint\n\n\nConstruct a StateConstraint object wrapping the function f.\n\nArguments\n\nf::Function: The function defining the state constraint.\nautonomous::Bool: Whether the system is autonomous (default uses __autonomous()).\nvariable::Bool: Whether the function depends on additional variables (default uses __variable()).\n\nReturns\n\nA StateConstraint instance parameterized by the type of f and time/variable dependence.\n\nExample\n\njulia> sc = StateConstraint(x -> x .- 1) # Autonomous, fixed variable by default\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.Time","page":"Types","title":"CTFlows.Time","text":"Alias for scalar time variables.\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.TimeDependence","page":"Types","title":"CTFlows.TimeDependence","text":"abstract type TimeDependence\n\nBase abstract type representing the dependence of a function on time.\n\nUsed as a trait to distinguish autonomous vs. non-autonomous functions.\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.Times","page":"Types","title":"CTFlows.Times","text":"Alias for a vector of time points.\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.VariableDependence","page":"Types","title":"CTFlows.VariableDependence","text":"abstract type VariableDependence\n\nBase abstract type representing whether a function depends on an additional variable argument.\n\nUsed to distinguish fixed-argument functions from those with auxiliary parameters.\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.VectorField","page":"Types","title":"CTFlows.VectorField","text":"struct VectorField{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractVectorField{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\nRepresents a dynamical system dx/dt = f(...) as a vector field.\n\nFields\n\nf: a callable of the form:\nf(x)\nf(t, x)\nf(x, v)\nf(t, x, v)\n\nExample\n\nf(x) = [x[2], -x[1]]\nvf = VectorField{typeof(f), Autonomous, Fixed}(f)\nvf([1.0, 0.0])\n\n\n\n\n\n","category":"type"},{"location":"types.html#CTFlows.VectorField-Tuple{Function, Type{<:CTFlows.TimeDependence}, Type{<:CTFlows.VariableDependence}}","page":"Types","title":"CTFlows.VectorField","text":"VectorField(\n f::Function,\n TD::Type{<:CTFlows.TimeDependence},\n VD::Type{<:CTFlows.VariableDependence}\n) -> CTFlows.VectorField\n\n\nCreate a VectorField object with explicit time and variable dependence types.\n\nArguments\n\nf::Function: The vector field function.\nTD: Type indicating time dependence (Autonomous or NonAutonomous).\nVD: Type indicating variable dependence (Fixed or NonFixed).\n\nReturns\n\nA VectorField{typeof(f),TD,VD} object.\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.VectorField-Tuple{Function}","page":"Types","title":"CTFlows.VectorField","text":"VectorField(\n f::Function;\n autonomous,\n variable\n) -> CTFlows.VectorField\n\n\nCreate a VectorField object wrapping the function f.\n\nArguments\n\nf::Function: The vector field function.\nautonomous::Bool (optional): If true, the vector field is autonomous (time-independent). Defaults to __autonomous().\nvariable::Bool (optional): If true, the vector field depends on control or decision variables (non-fixed). Defaults to __variable().\n\nReturns\n\nA VectorField{typeof(f),TD,VD} object where TD encodes time dependence and VD encodes variable dependence.\n\nDetails\n\nThe VectorField object can be called with different argument signatures depending on the time and variable dependence.\n\nExamples\n\njulia> f(x) = [-x[2], x[1]]\njulia> vf = VectorField(f, autonomous=true, variable=false)\njulia> vf([1.0, 0.0]) # returns [-0.0, 1.0]\n\n\n\n\n\n","category":"method"},{"location":"types.html#CTFlows.ctNumber","page":"Types","title":"CTFlows.ctNumber","text":"Base scalar type used in continuous-time models, e.g. Float64 or Dual numbers.\n\n\n\n\n\n","category":"type"},{"location":"differential_geometry.html#Differential-Geometry","page":"Differential Geometry","title":"Differential Geometry","text":"","category":"section"},{"location":"differential_geometry.html#Index","page":"Differential Geometry","title":"Index","text":"Pages = [\"differential_geometry.md\"]\nModules = [CTFlows]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"differential_geometry.html#Documentation","page":"Differential Geometry","title":"Documentation","text":"","category":"section"},{"location":"differential_geometry.html#CTFlows.:โ…‹-Union{Tuple{V}, Tuple{CTFlows.VectorField{<:Function, CTFlows.Autonomous, V}, CTFlows.VectorField{<:Function, CTFlows.Autonomous, V}}} where V<:CTFlows.VariableDependence","page":"Differential Geometry","title":"CTFlows.:โ…‹","text":"\"Directional derivative\" of a vector field in the autonomous case, used internally for computing the Lie bracket.\n\nExample:\n\njulia> X = VectorField(x -> [x[2], -x[1]])\njulia> Y = VectorField(x -> [x[1], x[2]])\njulia> (X โ…‹ Y)([1, 2])\n[2, -1]\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.:โ…‹-Union{Tuple{V}, Tuple{CTFlows.VectorField{<:Function, CTFlows.NonAutonomous, V}, CTFlows.VectorField{<:Function, CTFlows.NonAutonomous, V}}} where V<:CTFlows.VariableDependence","page":"Differential Geometry","title":"CTFlows.:โ…‹","text":"\"Directional derivative\" of a vector field in the nonautonomous case, used internally for computing the Lie bracket.\n\nExample:\n\njulia> X = VectorField((t, x, v) -> [t + v[1] + v[2] + x[2], -x[1]], NonFixed, NonAutonomous)\njulia> Y = VectorField((t, x, v) -> [v[1] + v[2] + x[1], x[2]], NonFixed, NonAutonomous)\njulia> (X โ…‹ Y)(1, [1, 2], [2, 3])\n[8, -1]\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.:โ‹…-Tuple{CTFlows.VectorField{<:Function, CTFlows.Autonomous}, Function}","page":"Differential Geometry","title":"CTFlows.:โ‹…","text":"Lie derivative of a scalar function along a vector field in the autonomous case.\n\nExample:\n\njulia> ฯ† = x -> [x[2], -x[1]]\njulia> X = VectorField(ฯ†)\njulia> f = x -> x[1]^2 + x[2]^2\njulia> (Xโ‹…f)([1, 2])\n0\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.:โ‹…-Tuple{CTFlows.VectorField{<:Function, CTFlows.NonAutonomous}, Function}","page":"Differential Geometry","title":"CTFlows.:โ‹…","text":"Lie derivative of a scalar function along a vector field in the nonautonomous case.\n\nExample:\n\njulia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]\njulia> X = VectorField(ฯ†, NonAutonomous, NonFixed)\njulia> f = (t, x, v) -> t + x[1]^2 + x[2]^2\njulia> (Xโ‹…f)(1, [1, 2], [2, 1])\n10\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.:โ‹…-Tuple{Function, Function}","page":"Differential Geometry","title":"CTFlows.:โ‹…","text":"Lie derivative of a scalar function along a function (considered autonomous and non-variable).\n\nExample:\n\njulia> ฯ† = x -> [x[2], -x[1]]\njulia> f = x -> x[1]^2 + x[2]^2\njulia> (ฯ†โ‹…f)([1, 2])\n0\njulia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]\njulia> f = (t, x, v) -> t + x[1]^2 + x[2]^2\njulia> (ฯ†โ‹…f)(1, [1, 2], [2, 1])\nMethodError\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Lie-Tuple{CTFlows.VectorField, Function}","page":"Differential Geometry","title":"CTFlows.Lie","text":"Lie derivative of a scalar function along a vector field.\n\nExample:\n\njulia> ฯ† = x -> [x[2], -x[1]]\njulia> X = VectorField(ฯ†)\njulia> f = x -> x[1]^2 + x[2]^2\njulia> Lie(X,f)([1, 2])\n0\njulia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]\njulia> X = VectorField(ฯ†, NonAutonomous, NonFixed)\njulia> f = (t, x, v) -> t + x[1]^2 + x[2]^2\njulia> Lie(X, f)(1, [1, 2], [2, 1])\n10\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Lie-Tuple{Function, Function}","page":"Differential Geometry","title":"CTFlows.Lie","text":"Lie derivative of a scalar function along a function with specified dependencies.\n\nExample:\n\njulia> ฯ† = x -> [x[2], -x[1]]\njulia> f = x -> x[1]^2 + x[2]^2\njulia> Lie(ฯ†,f)([1, 2])\n0\njulia> ฯ† = (t, x, v) -> [t + x[2] + v[1], -x[1] + v[2]]\njulia> f = (t, x, v) -> t + x[1]^2 + x[2]^2\njulia> Lie(ฯ†, f, autonomous=false, variable=true)(1, [1, 2], [2, 1])\n10\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Lie-Union{Tuple{V}, Tuple{CTFlows.VectorField{<:Function, CTFlows.Autonomous, V}, CTFlows.VectorField{<:Function, CTFlows.Autonomous, V}}} where V<:CTFlows.VariableDependence","page":"Differential Geometry","title":"CTFlows.Lie","text":"Lie bracket of two vector fields in the autonomous case.\n\nExample:\n\njulia> f = x -> [x[2], 2x[1]]\njulia> g = x -> [3x[2], -x[1]]\njulia> X = VectorField(f)\njulia> Y = VectorField(g)\njulia> Lie(X, Y)([1, 2])\n[7, -14]\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Lie-Union{Tuple{V}, Tuple{CTFlows.VectorField{<:Function, CTFlows.NonAutonomous, V}, CTFlows.VectorField{<:Function, CTFlows.NonAutonomous, V}}} where V<:CTFlows.VariableDependence","page":"Differential Geometry","title":"CTFlows.Lie","text":"Lie bracket of two vector fields in the nonautonomous case.\n\nExample:\n\njulia> f = (t, x, v) -> [t + x[2] + v, -2x[1] - v]\njulia> g = (t, x, v) -> [t + 3x[2] + v, -x[1] - v]\njulia> X = VectorField(f, NonAutonomous, NonFixed)\njulia> Y = VectorField(g, NonAutonomous, NonFixed)\njulia> Lie(X, Y)(1, [1, 2], 1)\n[-7, 12]\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Lift-Tuple{CTFlows.VectorField}","page":"Differential Geometry","title":"CTFlows.Lift","text":"Lift(\n X::CTFlows.VectorField\n) -> CTFlows.HamiltonianLift{CTFlows.VectorField{TF, TD, VD}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\n\nConstruct the Hamiltonian lift of a VectorField.\n\nArguments\n\nX::VectorField: The vector field to lift. Its signature determines if it is autonomous and/or variable.\n\nReturns\n\nA HamiltonianLift callable object representing the Hamiltonian lift of X.\n\nExamples\n\njulia> HL = Lift(VectorField(x -> [x[1]^2, x[2]^2], autonomous=true, variable=false))\njulia> HL([1, 0], [0, 1]) # returns 0\n\njulia> HL2 = Lift(VectorField((t, x, v) -> [t + x[1]^2, x[2]^2 + v], autonomous=false, variable=true))\njulia> HL2(1, [1, 0], [0, 1], 1) # returns 1\n\njulia> H = Lift(x -> 2x)\njulia> H(1, 1) # returns 2\n\njulia> H2 = Lift((t, x, v) -> 2x + t - v, autonomous=false, variable=true)\njulia> H2(1, 1, 1, 1) # returns 2\n\n# Alternative syntax using symbols for autonomy and variability\njulia> H3 = Lift((t, x, v) -> 2x + t - v, NonAutonomous, NonFixed)\njulia> H3(1, 1, 1, 1) # returns 2\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Lift-Tuple{Function}","page":"Differential Geometry","title":"CTFlows.Lift","text":"Lift(\n X::Function;\n autonomous,\n variable\n) -> CTFlows.var\"#21#22\"{<:Function}\n\n\nConstruct the Hamiltonian lift of a function.\n\nArguments\n\nX::Function: The function representing the vector field.\nautonomous::Bool=true: Whether the function is autonomous (time-independent).\nvariable::Bool=false: Whether the function depends on an additional variable argument.\n\nReturns\n\nA callable function computing the Hamiltonian lift, \n\n(and variants depending on autonomous and variable).\n\nDetails\n\nDepending on the autonomous and variable flags, the returned function has one of the following call signatures:\n\n(x, p) if autonomous=true and variable=false\n(x, p, v) if autonomous=true and variable=true\n(t, x, p) if autonomous=false and variable=false\n(t, x, p, v) if autonomous=false and variable=true\n\nExamples\n\njulia> H = Lift(x -> 2x)\njulia> H(1, 1) # returns 2\n\njulia> H2 = Lift((t, x, v) -> 2x + t - v, autonomous=false, variable=true)\njulia> H2(1, 1, 1, 1) # returns 2\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Poisson-Tuple{Function, Function}","page":"Differential Geometry","title":"CTFlows.Poisson","text":"Poisson(\n f::Function,\n g::Function;\n autonomous,\n variable\n) -> CTFlows.Hamiltonian\n\n\nPoisson bracket of two functions. The time and variable dependence are specified with keyword arguments.\n\nReturns a Hamiltonian computed from the functions promoted as Hamiltonians.\n\nExample\n\njulia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2\njulia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]\njulia> Poisson(f, g)([1, 2], [2, 1]) # -20\n\njulia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]\njulia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]\njulia> Poisson(f, g, autonomous=false, variable=true)(2, [1, 2], [2, 1], [4, 4]) # -76\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Poisson-Union{Tuple{VD}, Tuple{TD}, Tuple{CTFlows.AbstractHamiltonian{TD, VD}, Function}} where {TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}","page":"Differential Geometry","title":"CTFlows.Poisson","text":"Poisson(\n f::CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence},\n g::Function\n) -> CTFlows.Hamiltonian\n\n\nPoisson bracket of a Hamiltonian and a function.\n\nReturns a Hamiltonian representing {f, g} where f is already a Hamiltonian.\n\nExample\n\njulia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2\njulia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]\njulia> F = Hamiltonian(f)\njulia> Poisson(F, g)([1, 2], [2, 1]) # -20\n\njulia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]\njulia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]\njulia> F = Hamiltonian(f, autonomous=false, variable=true)\njulia> Poisson(F, g)(2, [1, 2], [2, 1], [4, 4]) # -76\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Poisson-Union{Tuple{VD}, Tuple{TD}, Tuple{Function, CTFlows.AbstractHamiltonian{TD, VD}}} where {TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}","page":"Differential Geometry","title":"CTFlows.Poisson","text":"Poisson(\n f::Function,\n g::CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n) -> CTFlows.Hamiltonian\n\n\nPoisson bracket of a function and a Hamiltonian.\n\nReturns a Hamiltonian representing {f, g} where g is already a Hamiltonian.\n\nExample\n\njulia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2\njulia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]\njulia> G = Hamiltonian(g)\njulia> Poisson(f, G)([1, 2], [2, 1]) # -20\n\njulia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]\njulia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]\njulia> G = Hamiltonian(g, autonomous=false, variable=true)\njulia> Poisson(f, G)(2, [1, 2], [2, 1], [4, 4]) # -76\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Poisson-Union{Tuple{V}, Tuple{CTFlows.AbstractHamiltonian{CTFlows.Autonomous, V}, CTFlows.AbstractHamiltonian{CTFlows.Autonomous, V}}} where V<:CTFlows.VariableDependence","page":"Differential Geometry","title":"CTFlows.Poisson","text":"Poisson(\n f::CTFlows.AbstractHamiltonian{CTFlows.Autonomous, V<:CTFlows.VariableDependence},\n g::CTFlows.AbstractHamiltonian{CTFlows.Autonomous, V<:CTFlows.VariableDependence}\n) -> Any\n\n\nPoisson bracket of two Hamiltonian functions (subtype of AbstractHamiltonian). Autonomous case.\n\nReturns a Hamiltonian representing the Poisson bracket {f, g} of two autonomous Hamiltonian functions f and g.\n\nExample\n\njulia> f = (x, p) -> x[2]^2 + 2x[1]^2 + p[1]^2\njulia> g = (x, p) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1]\njulia> F = Hamiltonian(f)\njulia> G = Hamiltonian(g)\njulia> Poisson(f, g)([1, 2], [2, 1]) # -20\njulia> Poisson(f, G)([1, 2], [2, 1]) # -20\njulia> Poisson(F, g)([1, 2], [2, 1]) # -20\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Poisson-Union{Tuple{V}, Tuple{CTFlows.AbstractHamiltonian{CTFlows.NonAutonomous, V}, CTFlows.AbstractHamiltonian{CTFlows.NonAutonomous, V}}} where V<:CTFlows.VariableDependence","page":"Differential Geometry","title":"CTFlows.Poisson","text":"Poisson(\n f::CTFlows.AbstractHamiltonian{CTFlows.NonAutonomous, V<:CTFlows.VariableDependence},\n g::CTFlows.AbstractHamiltonian{CTFlows.NonAutonomous, V<:CTFlows.VariableDependence}\n) -> Any\n\n\nPoisson bracket of two Hamiltonian functions. Non-autonomous case.\n\nReturns a Hamiltonian representing {f, g} where f and g are time-dependent.\n\nExample\n\njulia> f = (t, x, p, v) -> t*v[1]*x[2]^2 + 2x[1]^2 + p[1]^2 + v[2]\njulia> g = (t, x, p, v) -> 3x[2]^2 - x[1]^2 + p[2]^2 + p[1] + t - v[2]\njulia> F = Hamiltonian(f, autonomous=false, variable=true)\njulia> G = Hamiltonian(g, autonomous=false, variable=true)\njulia> Poisson(F, G)(2, [1, 2], [2, 1], [4, 4]) # -76\njulia> Poisson(f, g, NonAutonomous, NonFixed)(2, [1, 2], [2, 1], [4, 4]) # -76\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.Poisson-Union{Tuple{V}, Tuple{T}, Tuple{CTFlows.HamiltonianLift{T, V}, CTFlows.HamiltonianLift{T, V}}} where {T<:CTFlows.TimeDependence, V<:CTFlows.VariableDependence}","page":"Differential Geometry","title":"CTFlows.Poisson","text":"Poisson(\n f::CTFlows.HamiltonianLift{T<:CTFlows.TimeDependence, V<:CTFlows.VariableDependence},\n g::CTFlows.HamiltonianLift{T<:CTFlows.TimeDependence, V<:CTFlows.VariableDependence}\n)\n\n\nPoisson bracket of two HamiltonianLift vector fields.\n\nReturns the HamiltonianLift corresponding to the Lie bracket of vector fields f.X and g.X.\n\nExample\n\njulia> f = x -> [x[1]^2 + x[2]^2, 2x[1]^2]\njulia> g = x -> [3x[2]^2, x[2] - x[1]^2]\njulia> F = Lift(f)\njulia> G = Lift(g)\njulia> Poisson(F, G)([1, 2], [2, 1]) # -64\n\njulia> f = (t, x, v) -> [t*v[1]*x[2]^2, 2x[1]^2 + v[2]]\njulia> g = (t, x, v) -> [3x[2]^2 - x[1]^2, t - v[2]]\njulia> F = Lift(f, NonAutonomous, NonFixed)\njulia> G = Lift(g, NonAutonomous, NonFixed)\njulia> Poisson(F, G)(2, [1, 2], [2, 1], [4, 4]) # 100\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.โˆ‚โ‚œ-Tuple{Any}","page":"Differential Geometry","title":"CTFlows.โˆ‚โ‚œ","text":"Partial derivative with respect to time of a function.\n\nExample:\n\njulia> โˆ‚โ‚œ((t,x) -> t*x)(0,8)\n8\n\n\n\n\n\n","category":"method"},{"location":"differential_geometry.html#CTFlows.@Lie-Tuple{Expr, Vararg{Any}}","page":"Differential Geometry","title":"CTFlows.@Lie","text":"Compute Lie or Poisson brackets.\n\nThis macro provides a unified notation to define recursively nested Lie brackets (for vector fields) or Poisson brackets (for Hamiltonians).\n\nSyntax\n\n@Lie [F, G]: computes the Lie bracket [F, G] of two vector fields.\n@Lie [[F, G], H]: supports arbitrarily nested Lie brackets.\n@Lie {H, K}: computes the Poisson bracket {H, K} of two Hamiltonians.\n@Lie {{H, K}, L}: supports arbitrarily nested Poisson brackets.\n@Lie expr autonomous = false: specifies a non-autonomous system.\n@Lie expr variable = true: indicates presence of an auxiliary variable v.\n\nKeyword-like arguments can be provided to control the evaluation context for Poisson brackets with raw functions:\n\nautonomous = Bool: whether the system is time-independent (default: true).\nvariable = Bool: whether the system depends on an extra variable v (default: false).\n\nBracket type detection\n\nSquare brackets [...] denote Lie brackets between VectorField objects.\nCurly brackets {...} denote Poisson brackets between Hamiltonian objects or between raw functions.\nThe macro automatically dispatches to Lie or Poisson depending on the input pattern.\n\nReturn\n\nA callable object representing the specified Lie or Poisson bracket expression. The returned function can be evaluated like any other vector field or Hamiltonian.\n\n\n\nExamples\n\nโ–  Lie brackets with VectorField (autonomous)\n\njulia> F1 = VectorField(x -> [0, -x[3], x[2]])\njulia> F2 = VectorField(x -> [x[3], 0, -x[1]])\njulia> L = @Lie [F1, F2]\njulia> L([1.0, 2.0, 3.0])\n3-element Vector{Float64}:\n 2.0\n -1.0\n 0.0\n\nโ–  Lie brackets with VectorField (non-autonomous, with auxiliary variable)\n\njulia> F1 = VectorField((t, x, v) -> [0, -x[3], x[2]]; autonomous=false, variable=true)\njulia> F2 = VectorField((t, x, v) -> [x[3], 0, -x[1]]; autonomous=false, variable=true)\njulia> L = @Lie [F1, F2]\njulia> L(0.0, [1.0, 2.0, 3.0], 1.0)\n3-element Vector{Float64}:\n 2.0\n -1.0\n 0.0\n\nโ–  Poisson brackets with Hamiltonian (autonomous)\n\njulia> H1 = Hamiltonian((x, p) -> x[1]^2 + p[2]^2)\njulia> H2 = Hamiltonian((x, p) -> x[2]^2 + p[1]^2)\njulia> P = @Lie {H1, H2}\njulia> P([1.0, 1.0], [3.0, 2.0])\n-4.0\n\nโ–  Poisson brackets with Hamiltonian (non-autonomous, with variable)\n\njulia> H1 = Hamiltonian((t, x, p, v) -> x[1]^2 + p[2]^2 + v; autonomous=false, variable=true)\njulia> H2 = Hamiltonian((t, x, p, v) -> x[2]^2 + p[1]^2 + v; autonomous=false, variable=true)\njulia> P = @Lie {H1, H2}\njulia> P(1.0, [1.0, 3.0], [4.0, 2.0], 3.0)\n8.0\n\nโ–  Poisson brackets from raw functions\n\njulia> H1 = (x, p) -> x[1]^2 + p[2]^2\njulia> H2 = (x, p) -> x[2]^2 + p[1]^2\njulia> P = @Lie {H1, H2}\njulia> P([1.0, 1.0], [3.0, 2.0])\n-4.0\n\nโ–  Poisson bracket with non-autonomous raw functions\n\njulia> H1 = (t, x, p) -> x[1]^2 + p[2]^2 + t\njulia> H2 = (t, x, p) -> x[2]^2 + p[1]^2 + t\njulia> P = @Lie {H1, H2} autonomous = false\njulia> P(3.0, [1.0, 2.0], [4.0, 1.0])\n-8.0\n\nโ–  Nested brackets\n\njulia> F = VectorField(x -> [-x[1], x[2], x[3]])\njulia> G = VectorField(x -> [x[3], -x[2], 0])\njulia> H = VectorField(x -> [0, 0, -x[1]])\njulia> nested = @Lie [[F, G], H]\njulia> nested([1.0, 2.0, 3.0])\n3-element Vector{Float64}:\n 2.0\n 0.0\n -6.0\n\njulia> H1 = (x, p) -> x[2]*x[1]^2 + p[1]^2\njulia> H2 = (x, p) -> x[1]*p[2]^2\njulia> H3 = (x, p) -> x[1]*p[2] + x[2]*p[1]\njulia> nested_poisson = @Lie {{H1, H2}, H3}\njulia> nested_poisson([1.0, 2.0], [0.5, 1.0])\n14.0\n\nโ–  Mixed expressions with arithmetic\n\njulia> F1 = VectorField(x -> [0, -x[3], x[2]])\njulia> F2 = VectorField(x -> [x[3], 0, -x[1]])\njulia> x = [1.0, 2.0, 3.0]\njulia> @Lie [F1, F2](x) + 3 * [F1, F2](x)\n3-element Vector{Float64}:\n 8.0\n -4.0\n 0.0\n\njulia> H1 = (x, p) -> x[1]^2\njulia> H2 = (x, p) -> p[1]^2\njulia> H3 = (x, p) -> x[1]*p[1]\njulia> x = [1.0, 2.0, 3.0]\njulia> p = [3.0, 2.0, 1.0]\njulia> @Lie {H1, H2}(x, p) + 2 * {H2, H3}(x, p)\n24.0\n\n\n\n\n\n","category":"macro"},{"location":"vector_field.html#Vector-Field","page":"Vector Field","title":"Vector Field","text":"","category":"section"},{"location":"vector_field.html#Index","page":"Vector Field","title":"Index","text":"Pages = [\"vector_field.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"vector_field.html#Documentation","page":"Vector Field","title":"Documentation","text":"","category":"section"},{"location":"vector_field.html#CTFlows.Flow-Tuple{CTFlows.VectorField}","page":"Vector Field","title":"CTFlows.Flow","text":"Flow(\n vf::CTFlows.VectorField;\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> CTFlowsODE.VectorFieldFlow\n\n\nConstructs a flow object for a classical (non-Hamiltonian) vector field.\n\nThis creates a VectorFieldFlow that integrates the ODE system dx/dt = vf(t, x, v) using DifferentialEquations.jl. It handles both fixed and parametric dynamics, as well as jump discontinuities and event stopping.\n\nKeyword Arguments\n\nalg, abstol, reltol, saveat, internalnorm: Solver options.\nkwargs_Flow...: Additional arguments passed to the solver configuration.\n\nExample\n\njulia> vf(t, x, v) = -v * x\njulia> flow = CTFlows.Flow(CTFlows.VectorField(vf))\njulia> x1 = flow(0.0, 1.0, 1.0)\n\n\n\n\n\n","category":"method"},{"location":"vector_field.html#CTFlowsODE.vector_field_usage-NTuple{5, Any}","page":"Vector Field","title":"CTFlowsODE.vector_field_usage","text":"vector_field_usage(\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm;\n kwargs_Flow...\n) -> Any\n\n\nReturns a function that solves the ODE associated with a classical vector field.\n\nThis utility creates a flow integrator for systems of the form dx/dt = f(t, x, v), where x is the state and v is an external parameter. It supports integration over a time span as well as direct queries for final state evaluation.\n\nTwo overloads are returned:\n\nf(tspan, x0, v=default_variable; kwargs...) returns the full solution trajectory.\nf(t0, x0, tf, v=default_variable; kwargs...) returns only the final state x(tf).\n\nInternally uses OrdinaryDiffEq.solve, with support for stopping times and jump discontinuities.\n\nArguments\n\nalg: Integration algorithm (e.g. Tsit5()).\nabstol, reltol: Absolute and relative tolerances.\nsaveat: Output time step or vector of times.\ninternalnorm: Norm used for adaptive integration.\nkwargs_Flow...: Default solver options (overridden by explicit kwargs at call site).\n\nExample\n\njulia> vf = (t, x, v) -> -v * x\njulia> flowfun = vector_field_usage(Tsit5(), 1e-8, 1e-8, 0.1, norm)\njulia> xf = flowfun(0.0, 1.0, 1.0, 2.0)\n\n\n\n\n\n","category":"method"},{"location":"default.html#Default","page":"Default","title":"Default","text":"","category":"section"},{"location":"default.html#Index","page":"Default","title":"Index","text":"Pages = [\"default.md\"]\nModules = [CTFlows]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"default.html#Documentation","page":"Default","title":"Documentation","text":"","category":"section"},{"location":"default.html#CTFlows.__autonomous-Tuple{CTModels.OCP.Model{CTModels.OCP.Autonomous}}","page":"Default","title":"CTFlows.__autonomous","text":"__autonomous(\n _::CTModels.OCP.Model{CTModels.OCP.Autonomous}\n) -> Bool\n\n\nReturn true for a model declared as CTModels.Autonomous.\n\nUsed to determine whether a model has time-independent dynamics.\n\n\n\n\n\n","category":"method"},{"location":"default.html#CTFlows.__autonomous-Tuple{CTModels.OCP.Model{CTModels.OCP.NonAutonomous}}","page":"Default","title":"CTFlows.__autonomous","text":"__autonomous(\n _::CTModels.OCP.Model{CTModels.OCP.NonAutonomous}\n) -> Bool\n\n\nReturn false for a model declared as CTModels.NonAutonomous.\n\nUsed to identify models whose dynamics depend explicitly on time.\n\n\n\n\n\n","category":"method"},{"location":"default.html#CTFlows.__autonomous-Tuple{}","page":"Default","title":"CTFlows.__autonomous","text":"__autonomous() -> Bool\n\n\nReturn true by default, assuming the problem is autonomous.\n\nThis is the fallback for generic cases when no model is provided.\n\n\n\n\n\n","category":"method"},{"location":"default.html#CTFlows.__variable-Tuple{CTModels.OCP.Model}","page":"Default","title":"CTFlows.__variable","text":"__variable(ocp::CTModels.OCP.Model) -> Bool\n\n\nReturn true if the model has one or more external variables.\n\nUsed to check whether the problem is parameterized by an external vector v.\n\n\n\n\n\n","category":"method"},{"location":"default.html#CTFlows.__variable-Tuple{}","page":"Default","title":"CTFlows.__variable","text":"__variable() -> Bool\n\n\nReturn false by default, assuming no external variable is used.\n\nFallback for cases where no model is given.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem.html#Optimal-Control-Problem","page":"Optimal Control Problem","title":"Optimal Control Problem","text":"","category":"section"},{"location":"optimal_control_problem.html#Index","page":"Optimal Control Problem","title":"Index","text":"Pages = [\"optimal_control_problem.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"optimal_control_problem.html#Documentation","page":"Optimal Control Problem","title":"Documentation","text":"","category":"section"},{"location":"optimal_control_problem.html#CTFlows.Flow-Tuple{CTModels.OCP.Model, CTFlows.ControlLaw}","page":"Optimal Control Problem","title":"CTFlows.Flow","text":"Flow(\n ocp::CTModels.OCP.Model,\n u::CTFlows.ControlLaw;\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConstruct a flow for an optimal control problem using a given control law.\n\nThis method builds the Hamiltonian system associated with the optimal control problem (ocp) and integrates the corresponding stateโ€“costate dynamics using the specified control law u.\n\nArguments\n\nocp::CTModels.Model: An optimal control problem defined using CTModels.\nu::CTFlows.ControlLaw: A feedback control law generated by ControlLaw(...) or similar.\nalg: Integration algorithm (default inferred).\nabstol: Absolute tolerance for the ODE solver.\nreltol: Relative tolerance for the ODE solver.\nsaveat: Time points at which to save the solution.\ninternalnorm: Optional norm function used by the integrator.\nkwargs_Flow: Additional keyword arguments passed to the solver.\n\nReturns\n\nA flow object f such that:\n\nf(t0, x0, p0, tf) integrates the state and costate from t0 to tf.\nf((t0, tf), x0, p0) returns the full trajectory over the interval.\n\nExample\n\njulia> u = (x, p) -> p\njulia> f = Flow(ocp, ControlLaw(u))\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem.html#CTFlows.Flow-Tuple{CTModels.OCP.Model, Function, Function, Function}","page":"Optimal Control Problem","title":"CTFlows.Flow","text":"Flow(\n ocp::CTModels.OCP.Model,\n u::Function,\n g::Function,\n ฮผ::Function;\n autonomous,\n variable,\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConstruct a flow from a raw feedback control, constraint, and multiplier.\n\nThis version is for defining flows directly from user functions without wrapping them into ControlLaw, Constraint, or Multiplier types. Automatically wraps and adapts them based on time dependence.\n\nArguments\n\nocp::CTModels.Model: The optimal control problem.\nu::Function: Control law.\ng::Function: Constraint.\nฮผ::Function: Multiplier.\nautonomous::Bool: Whether the system is autonomous.\nvariable::Bool: Whether time is a free variable.\nalg, abstol, reltol, saveat, internalnorm: Solver parameters.\nkwargs_Flow: Additional options.\n\nReturns\n\nA Flow object ready for trajectory integration.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem.html#CTFlows.Flow-Tuple{CTModels.OCP.Model, Function}","page":"Optimal Control Problem","title":"CTFlows.Flow","text":"Flow(\n ocp::CTModels.OCP.Model,\n u::Function;\n autonomous,\n variable,\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConstruct a flow for an optimal control problem using a control function in feedback form.\n\nThis method constructs the Hamiltonian and integrates the associated stateโ€“costate dynamics using a raw function u. It automatically wraps u as a control law.\n\nArguments\n\nocp::CTModels.Model: The optimal control problem.\nu::Function: A feedback control function:\nIf ocp is autonomous: u(x, p)\nIf non-autonomous: u(t, x, p)\nautonomous::Bool: Whether the control law depends on time.\nvariable::Bool: Whether the OCP involves variable time (e.g., free final time).\nalg, abstol, reltol, saveat, internalnorm: ODE solver parameters.\nkwargs_Flow: Additional options.\n\nReturns\n\nA Flow object compatible with function call interfaces for state propagation.\n\nExample\n\njulia> u = (t, x, p) -> t + p\njulia> f = Flow(ocp, u)\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem.html#CTFlows.Flow-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, Union{CTFlows.ControlLaw{<:Function, T, V}, CTFlows.FeedbackControl{<:Function, T, V}}, Union{CTFlows.MixedConstraint{<:Function, T, V}, CTFlows.StateConstraint{<:Function, T, V}}, CTFlows.Multiplier{<:Function, T, V}}} where {T, V}","page":"Optimal Control Problem","title":"CTFlows.Flow","text":"Flow(\n ocp::CTModels.OCP.Model,\n u::Union{CTFlows.ControlLaw{<:Function, T, V}, CTFlows.FeedbackControl{<:Function, T, V}},\n g::Union{CTFlows.MixedConstraint{<:Function, T, V}, CTFlows.StateConstraint{<:Function, T, V}},\n ฮผ::CTFlows.Multiplier{<:Function, T, V};\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConstruct a flow for an optimal control problem with control and constraint multipliers in feedback form.\n\nThis variant constructs a Hamiltonian system incorporating both the control law and a multiplier law (e.g., for enforcing state or mixed constraints). All inputs must be consistent in time dependence.\n\nArguments\n\nocp::CTModels.Model: The optimal control problem.\nu::ControlLaw or FeedbackControl: Feedback control.\ng::StateConstraint or MixedConstraint: Constraint function.\nฮผ::Multiplier: Multiplier function.\nalg, abstol, reltol, saveat, internalnorm: Solver settings.\nkwargs_Flow: Additional options.\n\nReturns\n\nA Flow object that integrates the constrained Hamiltonian dynamics.\n\nExample\n\njulia> f = Flow(ocp, (x, p) -> p[1], (x, u) -> x[1] - 1, (x, p) -> x[1]+p[1])\n\nFor non-autonomous cases:\n\njulia> f = Flow(ocp, (t, x, p) -> t + p, (t, x, u) -> x - 1, (t, x, p) -> x+p)\n\nwarning: Warning\nAll input functions must match the autonomous/non-autonomous nature of the problem.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem.html#CTFlowsODE.__ocp_Flow-Tuple{CTModels.OCP.Model, CTFlows.Hamiltonian, CTFlows.ControlLaw, Vararg{Any, 5}}","page":"Optimal Control Problem","title":"CTFlowsODE.__ocp_Flow","text":"__ocp_Flow(\n ocp::CTModels.OCP.Model,\n h::CTFlows.Hamiltonian,\n u::CTFlows.ControlLaw,\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm;\n kwargs_Flow...\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nInternal helper: builds the OptimalControlFlow object from a Hamiltonian and control law.\n\nThis function assembles the RHS, constructs the integrator, and packages the flow object.\n\nArguments\n\nocp: The original optimal control problem.\nh: A Hamiltonian structure.\nu: A control law.\nalg, abstol, reltol, saveat, internalnorm: Integration parameters.\nkwargs_Flow: Additional parameters.\n\nReturns\n\nAn OptimalControlFlow object, callable as a function for integration.\n\nnote: Note\nThis function is internal and intended for constructing flows behind the scenes.\n\n\n\n\n\n","category":"method"},{"location":"ext_types.html#Extension-Types","page":"Extension Types","title":"Extension Types","text":"","category":"section"},{"location":"ext_types.html#Index","page":"Extension Types","title":"Index","text":"Pages = [\"ext_types.md\"]\nModules = [CTFlows, CTFlowsODE, CTModels]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"ext_types.html#Documentation","page":"Extension Types","title":"Documentation","text":"","category":"section"},{"location":"ext_types.html#CTFlowsODE.HamiltonianFlow","page":"Extension Types","title":"CTFlowsODE.HamiltonianFlow","text":"struct HamiltonianFlow <: CTFlowsODE.AbstractFlow{Union{Real, AbstractVector{<:Real}}, Union{Real, AbstractVector{<:Real}}}\n\nA flow object for integrating Hamiltonian dynamics in optimal control.\n\nRepresents the time evolution of a Hamiltonian system using the canonical form of Hamilton's equations. The struct holds the numerical integration setup and metadata for event handling.\n\nFields\n\nf::Function: Flow integrator function, called like f(t0, x0, p0, tf; ...).\nrhs!::Function: Right-hand side of the ODE system, used by solvers.\ntstops::Times: List of times at which integration should pause or apply discrete effects.\njumps::Vector{Tuple{Time,Costate}}: List of jump discontinuities for the costate at given times.\n\nUsage\n\nInstances of HamiltonianFlow are callable and forward arguments to the underlying flow function f.\n\nExample\n\njulia> flow = HamiltonianFlow(f, rhs!)\njulia> xf, pf = flow(0.0, x0, p0, 1.0)\n\n\n\n\n\n","category":"type"},{"location":"ext_types.html#CTFlowsODE.ODEFlow","page":"Extension Types","title":"CTFlowsODE.ODEFlow","text":"struct ODEFlow <: CTFlowsODE.AbstractFlow{Any, Any}\n\nGeneric flow object for arbitrary ODE systems with jumps and events.\n\nA catch-all flow for general-purpose ODE integration. Supports dynamic typing and arbitrary state structures.\n\nFields\n\nf::Function: Integrator function called with time span and initial conditions.\nrhs::Function: Right-hand side for the differential equation.\ntstops::Times: Times at which the integrator is forced to stop.\njumps::Vector{Tuple{Time,Any}}: User-defined jumps applied to the state during integration.\n\nExample\n\njulia> flow = ODEFlow(f, rhs)\njulia> result = flow(0.0, u0, 1.0)\n\n\n\n\n\n","category":"type"},{"location":"ext_types.html#CTFlowsODE.OptimalControlFlow","page":"Extension Types","title":"CTFlowsODE.OptimalControlFlow","text":"struct OptimalControlFlow{VD} <: CTFlowsODE.AbstractFlow{Union{Real, AbstractVector{<:Real}}, Union{Real, AbstractVector{<:Real}}}\n\nA flow object representing the solution of an optimal control problem.\n\nSupports Hamiltonian-based and classical formulations. Provides call overloads for different control settings:\n\nFixed external variables\nParametric (non-fixed) control problems\n\nFields\n\nf::Function: Main integrator that receives the RHS and other arguments.\nrhs!::Function: ODE right-hand side.\ntstops::Times: Times where the solver should stop (e.g., nonsmooth dynamics).\njumps::Vector{Tuple{Time,Costate}}: Costate jump conditions.\nfeedback_control::ControlLaw: Feedback law u(t, x, p, v).\nocp::Model: The optimal control problem definition.\nkwargs_Flow::Any: Extra solver arguments.\n\nCall Signatures\n\nF(t0, x0, p0, tf; kwargs...): Solves with fixed variable dimension.\nF(t0, x0, p0, tf, v; kwargs...): Solves with parameter v.\nF(tspan, x0, p0; ...): Solves and returns a full OptimalControlSolution.\n\nExample\n\njulia> flow = OptimalControlFlow(...)\njulia> sol = flow(0.0, x0, p0, 1.0)\njulia> opt_sol = flow((0.0, 1.0), x0, p0)\n\n\n\n\n\n","category":"type"},{"location":"ext_types.html#CTFlowsODE.OptimalControlFlowSolution","page":"Extension Types","title":"CTFlowsODE.OptimalControlFlowSolution","text":"struct OptimalControlFlowSolution\n\nWraps the low-level ODE solution, control feedback law, model structure, and problem parameters.\n\nFields\n\node_sol::Any: The ODE solution (from DifferentialEquations.jl).\nfeedback_control::ControlLaw: Feedback control law u(t, x, p, v).\nocp::Model: The optimal control model used.\nvariable::Variable: External or design parameters of the control problem.\n\nUsage\n\nYou can evaluate the flow solution like a callable ODE solution.\n\nExample\n\njulia> sol = OptimalControlFlowSolution(ode_sol, u, model, v)\njulia> x = sol(t)\n\n\n\n\n\n","category":"type"},{"location":"ext_types.html#CTFlowsODE.VectorFieldFlow","page":"Extension Types","title":"CTFlowsODE.VectorFieldFlow","text":"struct VectorFieldFlow <: CTFlowsODE.AbstractFlow{Union{Real, AbstractVector{<:Real}}, Union{Real, AbstractVector{<:Real}}}\n\nA flow object for integrating general vector field dynamics.\n\nUsed for systems where the vector field is given explicitly, rather than derived from a Hamiltonian. Useful in settings like controlled systems or classical mechanics outside the Hamiltonian framework.\n\nFields\n\nf::Function: Flow integrator function.\nrhs::Function: ODE right-hand side function.\ntstops::Times: Event times (e.g., to trigger callbacks).\njumps::Vector{Tuple{Time,State}}: Discrete jump events on the state trajectory.\n\nExample\n\njulia> flow = VectorFieldFlow(f, rhs)\njulia> xf = flow(0.0, x0, 1.0)\n\n\n\n\n\n","category":"type"},{"location":"ext_types.html#CTModels.OCP.Solution-Tuple{CTFlowsODE.OptimalControlFlowSolution}","page":"Extension Types","title":"CTModels.OCP.Solution","text":"Solution(\n ocfs::CTFlowsODE.OptimalControlFlowSolution;\n kwargs...\n) -> CTModels.OCP.Solution{TimeGridModelType, TimesModelType, StateModelType, ControlModelType, VariableModelType, ModelType, CostateModelType, Float64, DualModelType, CTModels.OCP.SolverInfos{Any, Dict{Symbol, Any}}} where {TimeGridModelType<:CTModels.OCP.AbstractTimeGridModel, TimesModelType<:CTModels.OCP.AbstractTimesModel, StateModelType<:CTModels.OCP.AbstractStateModel, ControlModelType<:CTModels.OCP.AbstractControlModel, VariableModelType<:CTModels.OCP.AbstractVariableModel, ModelType<:CTModels.OCP.AbstractModel, CostateModelType<:Function, DualModelType<:CTModels.OCP.AbstractDualModel}\n\n\nConstructs an OptimalControlSolution from an OptimalControlFlowSolution.\n\nThis evaluates the objective (Mayer and/or Lagrange costs), extracts the time-dependent state, costate, and control trajectories, and builds a full CTModels.Solution.\n\nReturns a CTModels.Solution ready for evaluation, reporting, or analysis.\n\nKeyword Arguments\n\nalg: Optional solver for computing Lagrange integral, if needed.\nAdditional kwargs passed to the internal solver.\n\nExample\n\njulia> sol = Solution(optflow_solution)\n\n\n\n\n\n","category":"method"},{"location":"utils.html#Utils","page":"Utils","title":"Utils","text":"","category":"section"},{"location":"utils.html#Index","page":"Utils","title":"Index","text":"Pages = [\"utils.md\"]\nModules = [CTFlows]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"utils.html#Documentation","page":"Utils","title":"Documentation","text":"","category":"section"},{"location":"utils.html#CTFlows.ctgradient","page":"Utils","title":"CTFlows.ctgradient","text":"ctgradient(f::Function, x::Real) -> Any\n\n\nCompute the derivative of a scalar function f at a scalar point x.\n\nArguments\n\nf::Function: A scalar-valued function.\nx::ctNumber: A scalar input.\n\nReturns\n\nThe derivative of f evaluated at x.\n\nExample\n\njulia> ctgradient(x -> x^2, 3.0) # returns 6.0\n\n\n\n\n\nctgradient(f::Function, x) -> Any\n\n\nCompute the gradient of a scalar function f at a vector point x.\n\nArguments\n\nf::Function: A scalar-valued function accepting a vector input.\nx: A vector of numbers.\n\nReturns\n\nA vector representing the gradient โˆ‡f(x).\n\nExample\n\njulia> ctgradient(x -> sum(x.^2), [1.0, 2.0]) # returns [2.0, 4.0]\n\n\n\n\n\nctgradient(X::CTFlows.VectorField, x) -> Any\n\n\nCompute the gradient of a VectorField at a given point.\n\nArguments\n\nX::VectorField: A vector field object with a callable function X.f.\nx: A scalar or vector input.\n\nReturns\n\nThe derivative or gradient depending on the type of x.\n\nExample\n\njulia> X = VectorField(x -> x^2)\njulia> ctgradient(X, 2.0) # returns 4.0\n\n\n\n\n\n","category":"function"},{"location":"utils.html#CTFlows.ctjacobian","page":"Utils","title":"CTFlows.ctjacobian","text":"ctjacobian(f::Function, x::Real) -> Any\n\n\nCompute the Jacobian of a vector-valued function f at a scalar point x.\n\nArguments\n\nf::Function: A vector-valued function.\nx::ctNumber: A scalar input.\n\nReturns\n\nA matrix representing the Jacobian Jf(x).\n\nExample\n\njulia> f(x) = [sin(x), cos(x)]\njulia> ctjacobian(f, 0.0) # returns a 2ร—1 matrix\n\n\n\n\n\nctjacobian(f::Function, x) -> Any\n\n\nCompute the Jacobian of a vector-valued function f at a vector point x.\n\nArguments\n\nf::Function: A vector-valued function.\nx: A vector input.\n\nReturns\n\nA matrix representing the Jacobian Jf(x).\n\nExample\n\njulia> f(x) = [x[1]^2, x[2]^2]\njulia> ctjacobian(f, [1.0, 2.0]) # returns [2.0 0.0; 0.0 4.0]\n\n\n\n\n\nctjacobian(X::CTFlows.VectorField, x) -> Any\n\n\nCompute the Jacobian of a VectorField at a given point.\n\nArguments\n\nX::VectorField: A vector field object with a callable function X.f.\nx: A scalar or vector input.\n\nReturns\n\nA matrix representing the Jacobian of X at x.\n\nExample\n\njulia> X = VectorField(x -> [x[1]^2, x[2]])\njulia> ctjacobian(X, [1.0, 3.0]) # returns [2.0 0.0; 0.0 1.0]\n\n\n\n\n\n","category":"function"},{"location":"optimal_control_problem_utils.html#Optimal-Control-Problem-Utils","page":"Optimal Control Problem Utils","title":"Optimal Control Problem Utils","text":"","category":"section"},{"location":"optimal_control_problem_utils.html#Index","page":"Optimal Control Problem Utils","title":"Index","text":"Pages = [\"optimal_control_problem_utils.md\"]\nModules = [CTFlows]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"optimal_control_problem_utils.html#Documentation","page":"Optimal Control Problem Utils","title":"Documentation","text":"","category":"section"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Tuple{CTModels.OCP.Model, Function, Any, Any}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::Function,\n g,\n ฮผ;\n autonomous,\n variable\n)\n\n\nOverload for control law as a raw function with autonomous and variable flags.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Tuple{CTModels.OCP.Model, Function}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::Function;\n autonomous,\n variable\n)\n\n\nHelper method to construct the Hamiltonian when control is given as a plain function.\n\nThe function is wrapped in a ControlLaw, and the flags autonomous and variable define its behavior type.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, CTFlows.ControlLaw{<:Function, T, V}, CTFlows.MixedConstraint{<:Function, T, V}, CTFlows.Multiplier{<:Function, T, V}}} where {T, V}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::CTFlows.ControlLaw{<:Function, T, V},\n g::CTFlows.MixedConstraint{<:Function, T, V},\n ฮผ::CTFlows.Multiplier{<:Function, T, V}\n) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##3\"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##4\"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}\n\n\nConstruct the Hamiltonian for a constrained optimal control problem.\n\nThis function supports multiple input types for the control law (u), path constraints (g), and multipliers (ฮผ), automatically adapting to autonomous/non-autonomous systems and fixed/non-fixed parameters.\n\nSupported input types\n\nu can be:\na raw function,\na ControlLaw object,\nor a FeedbackControl object.\ng can be:\na plain constraint function,\na MixedConstraint,\nor a StateConstraint.\nฮผ can be:\na function,\nor a Multiplier object.\n\nThe function normalizes these inputs to the appropriate types internally using multiple dispatch and pattern matching.\n\nArguments\n\nocp::CTModels.Model: The continuous-time optimal control problem.\nu: Control law, flexible input type as described.\ng: Path constraint, flexible input type as described.\nฮผ: Multiplier associated with the constraints.\nautonomous::Bool (optional keyword): Specifies if the system is autonomous.\nvariable::Bool (optional keyword): Specifies if the system parameters are variable.\n\nReturns\n\n(H, u): Tuple containing the Hamiltonian object H and the processed control law u.\n\nExamples\n\n# Using a raw function control law with autonomous system and fixed parameters\nH, u_processed = __create_hamiltonian(ocp, u_function, g_function, ฮผ_function; autonomous=true, variable=false)\n\n# Using a FeedbackControl control law\nH, u_processed = __create_hamiltonian(ocp, feedback_control, g_constraint, ฮผ_multiplier)\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, CTFlows.ControlLaw{<:Function, T, V}, CTFlows.MixedConstraint{<:Function, T, V}, Function}} where {T, V}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::CTFlows.ControlLaw{<:Function, T, V},\n g::CTFlows.MixedConstraint{<:Function, T, V},\n ฮผ::Function\n) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##3\"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##4\"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}\n\n\nOverload that wraps multiplier functions into Multiplier objects.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, CTFlows.ControlLaw{<:Function, T, V}, CTFlows.StateConstraint{<:Function, T, V}, Any}} where {T, V}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::CTFlows.ControlLaw{<:Function, T, V},\n g_::CTFlows.StateConstraint{<:Function, T, V},\n ฮผ\n) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##3\"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##4\"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}\n\n\nOverload that converts StateConstraint objects into MixedConstraint with appropriate signature adaptation.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, CTFlows.ControlLaw{<:Function, T, V}, Function, Any}} where {T, V}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::CTFlows.ControlLaw{<:Function, T, V},\n g::Function,\n ฮผ\n) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##3\"{f, u, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, g, ฮผ}, CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##4\"{f, u, fโฐ, pโฐ, s, g, ฮผ}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s, g, ฮผ}}, CTFlows.ControlLaw}\n\n\nOverload that wraps plain constraint functions into MixedConstraint objects.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, CTFlows.ControlLaw{<:Function, T, V}}} where {T, V}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u::CTFlows.ControlLaw{<:Function, T, V}\n) -> Tuple{Union{CTFlows.Hamiltonian{CTFlows.var\"#H#makeH##2\"{f, u, fโฐ, pโฐ, s}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u, fโฐ, pโฐ, s}, CTFlows.Hamiltonian{CTFlows.var\"#makeH##0#makeH##1\"{f, u}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {f, u}}, CTFlows.ControlLaw}\n\n\nConstruct and return the Hamiltonian for the given model and control law.\n\nThe Hamiltonian is built using model dynamics (and possibly a running cost) and returned as a callable function.\n\nReturns a tuple (H, u) where H is the Hamiltonian function and u is the control law.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__create_hamiltonian-Union{Tuple{V}, Tuple{T}, Tuple{CTModels.OCP.Model, CTFlows.FeedbackControl{<:Function, T, V}, Any, Any}} where {T, V}","page":"Optimal Control Problem Utils","title":"CTFlows.__create_hamiltonian","text":"__create_hamiltonian(\n ocp::CTModels.OCP.Model,\n u_::CTFlows.FeedbackControl{<:Function, T, V},\n g,\n ฮผ\n) -> Any\n\n\nOverload for feedback control laws that adapts the signature based on autonomy and variability.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__dynamics-Tuple{CTModels.OCP.Model}","page":"Optimal Control Problem Utils","title":"CTFlows.__dynamics","text":"__dynamics(\n ocp::CTModels.OCP.Model\n) -> CTFlows.Dynamics{CTFlows.var\"#__dynamics##0#__dynamics##1\"{ocp, n}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {ocp, n}\n\n\nReturn a Dynamics object built from the model's right-hand side function.\n\nThe returned function computes the state derivative dx/dt = f(t, x, u, v), wrapped to return either a scalar or vector depending on the model's state dimension.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__get_data_for_ocp_flow-Tuple{CTModels.OCP.Model}","page":"Optimal Control Problem Utils","title":"CTFlows.__get_data_for_ocp_flow","text":"__get_data_for_ocp_flow(\n ocp::CTModels.OCP.Model\n) -> Tuple{CTFlows.Dynamics{CTFlows.var\"#__dynamics##0#__dynamics##1\"{ocp, n}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {ocp, n}, Union{Nothing, CTFlows.Lagrange{<:Function, CTFlows.NonAutonomous, CTFlows.NonFixed}}, Int64, Float64}\n\n\nReturn internal components needed to construct the OCP Hamiltonian.\n\nReturns a tuple (f, fโฐ, pโฐ, s) where:\n\nf : system dynamics (Dynamics)\nfโฐ : optional Lagrange integrand (Lagrange or nothing)\npโฐ : constant multiplier for cost (typically -1)\ns : sign for minimization/maximization (+1 or -1)\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__is_min-Tuple{CTModels.OCP.Model}","page":"Optimal Control Problem Utils","title":"CTFlows.__is_min","text":"__is_min(ocp::CTModels.OCP.Model) -> Bool\n\n\nReturn true if the given model defines a minimization problem, false otherwise.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__lagrange-Tuple{CTModels.OCP.Model}","page":"Optimal Control Problem Utils","title":"CTFlows.__lagrange","text":"__lagrange(\n ocp::CTModels.OCP.Model\n) -> Union{Nothing, CTFlows.Lagrange{<:Function, CTFlows.NonAutonomous, CTFlows.NonFixed}}\n\n\nReturn a Lagrange object if the model includes an integrand cost; otherwise, return nothing.\n\nThe resulting function can be used to compute the running cost of the optimal control problem.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.__mayer-Tuple{CTModels.OCP.Model}","page":"Optimal Control Problem Utils","title":"CTFlows.__mayer","text":"__mayer(\n ocp::CTModels.OCP.Model\n) -> Union{Nothing, CTFlows.Mayer{<:Function, CTFlows.NonFixed}}\n\n\nReturn a Mayer object if the model includes a terminal cost; otherwise, return nothing.\n\nThe resulting function can be used to compute the final cost in the objective.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.makeH-Tuple{CTFlows.Dynamics, CTFlows.ControlLaw, CTFlows.Lagrange, Real, Real, CTFlows.MixedConstraint, CTFlows.Multiplier}","page":"Optimal Control Problem Utils","title":"CTFlows.makeH","text":"makeH(\n f::CTFlows.Dynamics,\n u::CTFlows.ControlLaw,\n fโฐ::CTFlows.Lagrange,\n pโฐ::Real,\n s::Real,\n g::CTFlows.MixedConstraint,\n ฮผ::CTFlows.Multiplier\n) -> CTFlows.var\"#H#makeH##4\"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}, CTFlows.Lagrange{TF2, TD2, VD2}, var\"#s179\", var\"#s1791\", CTFlows.MixedConstraint{TF3, TD3, VD3}, CTFlows.Multiplier{TF4, TD4, VD4}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence, TF2<:Function, TD2<:CTFlows.TimeDependence, VD2<:CTFlows.VariableDependence, var\"#s179\"<:Real, var\"#s1791\"<:Real, TF3<:Function, TD3<:CTFlows.TimeDependence, VD3<:CTFlows.VariableDependence, TF4<:Function, TD4<:CTFlows.TimeDependence, VD4<:CTFlows.VariableDependence}\n\n\nConstruct the Hamiltonian:\n\nH(t, x, p) = p โ‹… f(t, x, u(t, x, p)) + s pโฐ fโฐ(t, x, u(t, x, p)) + ฮผ(t, x, p) โ‹… g(t, x, u(t, x, p))\n\nCombines integrand cost and path constraints.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.makeH-Tuple{CTFlows.Dynamics, CTFlows.ControlLaw, CTFlows.Lagrange, Real, Real}","page":"Optimal Control Problem Utils","title":"CTFlows.makeH","text":"makeH(\n f::CTFlows.Dynamics,\n u::CTFlows.ControlLaw,\n fโฐ::CTFlows.Lagrange,\n pโฐ::Real,\n s::Real\n) -> CTFlows.var\"#H#makeH##2\"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}, CTFlows.Lagrange{TF2, TD2, VD2}, <:Real, <:Real} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence, TF2<:Function, TD2<:CTFlows.TimeDependence, VD2<:CTFlows.VariableDependence}\n\n\nConstruct the Hamiltonian:\n\nH(t, x, p) = p โ‹… f(t, x, u(t, x, p)) + s pโฐ fโฐ(t, x, u(t, x, p))\n\nIncludes a Lagrange integrand scaled by pโฐ and sign s.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.makeH-Tuple{CTFlows.Dynamics, CTFlows.ControlLaw, CTFlows.MixedConstraint, CTFlows.Multiplier}","page":"Optimal Control Problem Utils","title":"CTFlows.makeH","text":"makeH(\n f::CTFlows.Dynamics,\n u::CTFlows.ControlLaw,\n g::CTFlows.MixedConstraint,\n ฮผ::CTFlows.Multiplier\n) -> CTFlows.var\"#H#makeH##3\"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}, CTFlows.MixedConstraint{TF2, TD2, VD2}, CTFlows.Multiplier{TF3, TD3, VD3}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence, TF2<:Function, TD2<:CTFlows.TimeDependence, VD2<:CTFlows.VariableDependence, TF3<:Function, TD3<:CTFlows.TimeDependence, VD3<:CTFlows.VariableDependence}\n\n\nConstruct the Hamiltonian:\n\nH(t, x, p) = p โ‹… f(t, x, u(t, x, p)) + ฮผ(t, x, p) โ‹… g(t, x, u(t, x, p))\n\nIncludes state-control constraints and associated multipliers.\n\n\n\n\n\n","category":"method"},{"location":"optimal_control_problem_utils.html#CTFlows.makeH-Tuple{CTFlows.Dynamics, CTFlows.ControlLaw}","page":"Optimal Control Problem Utils","title":"CTFlows.makeH","text":"makeH(\n f::CTFlows.Dynamics,\n u::CTFlows.ControlLaw\n) -> CTFlows.var\"#makeH##0#makeH##1\"{CTFlows.Dynamics{TF, TD, VD}, CTFlows.ControlLaw{TF1, TD1, VD1}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence, TF1<:Function, TD1<:CTFlows.TimeDependence, VD1<:CTFlows.VariableDependence}\n\n\nConstruct the Hamiltonian:\n\nH(t, x, p) = p โ‹… f(t, x, u(t, x, p))\n\nThe function returns a callable H(t, x, p, v) where v is an optional additional parameter.\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#Concatenation","page":"Concatenation","title":"Concatenation","text":"","category":"section"},{"location":"concatenation.html#Index","page":"Concatenation","title":"Index","text":"Pages = [\"concatenation.md\"]\nModules = [CTFlows, CTFlowsODE, Base]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"concatenation.html#Documentation","page":"Concatenation","title":"Documentation","text":"","category":"section"},{"location":"concatenation.html#Base.:*-Union{Tuple{TF}, Tuple{TF, Tuple{Real, Any, TF}}} where TF<:CTFlowsODE.AbstractFlow","page":"Concatenation","title":"Base.:*","text":"*(\n F::CTFlowsODE.AbstractFlow,\n g::Tuple{Real, Any, TF<:CTFlowsODE.AbstractFlow}\n) -> Any\n\n\nShorthand for concatenate(F, g) when g is a tuple (t_switch, ฮท_switch, G) including a jump.\n\nArguments\n\nF::AbstractFlow: The first flow.\ng::Tuple{ctNumber, Any, AbstractFlow}: Tuple with switching time, jump value, and second flow.\n\nReturns\n\nA flow with a jump at t_switch and a switch from F to G.\n\nExample\n\njulia> F * (1.0, ฮท, G)\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#Base.:*-Union{Tuple{TF}, Tuple{TF, Tuple{Real, TF}}} where TF<:CTFlowsODE.AbstractFlow","page":"Concatenation","title":"Base.:*","text":"*(\n F::CTFlowsODE.AbstractFlow,\n g::Tuple{Real, TF<:CTFlowsODE.AbstractFlow}\n) -> Any\n\n\nShorthand for concatenate(F, g) when g is a tuple (t_switch, G).\n\nArguments\n\nF::AbstractFlow: The first flow.\ng::Tuple{ctNumber, AbstractFlow}: Tuple containing the switching time and second flow.\n\nReturns\n\nA new flow that switches from F to G at t_switch.\n\nExample\n\njulia> F * (1.0, G)\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.__concat_feedback_control-Tuple{CTFlowsODE.AbstractFlow, CTFlowsODE.AbstractFlow, Real}","page":"Concatenation","title":"CTFlowsODE.__concat_feedback_control","text":"__concat_feedback_control(\n F::CTFlowsODE.AbstractFlow,\n G::CTFlowsODE.AbstractFlow,\n t_switch::Real\n) -> CTFlows.ControlLaw{CTFlowsODE.var\"#_feedback_control#__concat_feedback_control##0\"{F, G, t_switch}, CTFlows.NonAutonomous, CTFlows.NonFixed} where {F, G, t_switch}\n\n\nConcatenate feedback control laws of two optimal control flows.\n\nArguments\n\nF, G: OptimalControlFlow instances.\nt_switch::Time: Switching time.\n\nReturns\n\nA ControlLaw that dispatches to F or G depending on t.\n\nExample\n\njulia> u = __concat_feedback_control(F, G, 2.0)\njulia> u(1.5, x, u, v) # from F\njulia> u(2.5, x, u, v) # from G\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.__concat_jumps","page":"Concatenation","title":"CTFlowsODE.__concat_jumps","text":"__concat_jumps(\n F::CTFlowsODE.AbstractFlow,\n G::CTFlowsODE.AbstractFlow\n) -> Any\n__concat_jumps(\n F::CTFlowsODE.AbstractFlow,\n G::CTFlowsODE.AbstractFlow,\n jump::Union{Nothing, Tuple{Real, Any}}\n) -> Any\n\n\nConcatenate the jumps of two flows, with optional extra jump at t_switch.\n\nArguments\n\nF, G: Flows with jump events.\njump: Optional tuple (t_switch, ฮท_switch) to insert.\n\nReturns\n\nCombined list of jumps.\n\nExample\n\njulia> __concat_jumps(F, G)\njulia> __concat_jumps(F, G, (1.0, ฮท))\n\n\n\n\n\n","category":"function"},{"location":"concatenation.html#CTFlowsODE.__concat_rhs-Tuple{CTFlowsODE.ODEFlow, CTFlowsODE.ODEFlow, Real}","page":"Concatenation","title":"CTFlowsODE.__concat_rhs","text":"__concat_rhs(\n F::CTFlowsODE.ODEFlow,\n G::CTFlowsODE.ODEFlow,\n t_switch::Real\n) -> CTFlowsODE.var\"#__concat_rhs##3#__concat_rhs##4\"{CTFlowsODE.ODEFlow, CTFlowsODE.ODEFlow, <:Real}\n\n\nConcatenate ODE right-hand sides with a switch at t_switch.\n\nArguments\n\nF, G: ODEFlow instances.\nt_switch::Time: Time at which to switch between flows.\n\nReturns\n\nA function of the form (x, v, t) -> ....\n\nExample\n\njulia> rhs = __concat_rhs(F, G, 0.5)\njulia> rhs(x, v, 0.4) # F.rhs\njulia> rhs(x, v, 0.6) # G.rhs\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.__concat_rhs-Tuple{CTFlowsODE.VectorFieldFlow, CTFlowsODE.VectorFieldFlow, Real}","page":"Concatenation","title":"CTFlowsODE.__concat_rhs","text":"__concat_rhs(\n F::CTFlowsODE.VectorFieldFlow,\n G::CTFlowsODE.VectorFieldFlow,\n t_switch::Real\n) -> CTFlowsODE.var\"#__concat_rhs##1#__concat_rhs##2\"{CTFlowsODE.VectorFieldFlow, CTFlowsODE.VectorFieldFlow, <:Real}\n\n\nConcatenate vector field right-hand sides with time-based switching.\n\nArguments\n\nF, G: VectorFieldFlow instances.\nt_switch::Time: Switching time.\n\nReturns\n\nA function of the form (x, v, t) -> ....\n\nExample\n\njulia> rhs = __concat_rhs(F, G, 2.0)\njulia> rhs(x, v, 1.0) # uses F.rhs\njulia> rhs(x, v, 3.0) # uses G.rhs\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.__concat_rhs-Union{Tuple{U}, Tuple{D}, Tuple{CTFlowsODE.AbstractFlow{D, U}, CTFlowsODE.AbstractFlow{D, U}, Real}} where {D, U}","page":"Concatenation","title":"CTFlowsODE.__concat_rhs","text":"__concat_rhs(\n F::CTFlowsODE.AbstractFlow{D, U},\n G::CTFlowsODE.AbstractFlow{D, U},\n t_switch::Real\n) -> CTFlowsODE.var\"#__concat_rhs##1#__concat_rhs##2\"{CTFlowsODE.VectorFieldFlow, CTFlowsODE.VectorFieldFlow, <:Real}\n\n\nConcatenate the right-hand sides of two flows F and G, switching at time t_switch.\n\nArguments\n\nF, G: Two flows of the same type.\nt_switch::Time: The switching time.\n\nReturns\n\nA function rhs! that dispatches to F.rhs! before t_switch, and to G.rhs! after.\n\nExample\n\njulia> rhs = __concat_rhs(F, G, 1.0)\njulia> rhs!(du, u, p, 0.5) # uses F.rhs!\njulia> rhs!(du, u, p, 1.5) # uses G.rhs!\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.__concat_tstops-Tuple{CTFlowsODE.AbstractFlow, CTFlowsODE.AbstractFlow, Real}","page":"Concatenation","title":"CTFlowsODE.__concat_tstops","text":"__concat_tstops(\n F::CTFlowsODE.AbstractFlow,\n G::CTFlowsODE.AbstractFlow,\n t_switch::Real\n) -> Any\n\n\nConcatenate the tstops (discontinuity times) of two flows and add the switching time.\n\nArguments\n\nF, G: Flows with tstops vectors.\nt_switch::Time: Switching time to include.\n\nReturns\n\nA sorted vector of unique tstops.\n\nExample\n\njulia> __concat_tstops(F, G, 1.0)\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.concatenate-Union{Tuple{TF}, Tuple{TF, Tuple{Real, Any, TF}}} where TF<:CTFlowsODE.AbstractFlow","page":"Concatenation","title":"CTFlowsODE.concatenate","text":"concatenate(\n F::CTFlowsODE.AbstractFlow,\n g::Tuple{Real, Any, TF<:CTFlowsODE.AbstractFlow}\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConcatenate two AbstractFlows and insert a jump at the switching time.\n\nArguments\n\nF::AbstractFlow\ng::Tuple{ctNumber,Any,AbstractFlow}: (t_switch, ฮท_switch, G)\n\nReturns\n\nA concatenated flow with the jump included.\n\nExample\n\njulia> F * (1.0, ฮท, G)\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.concatenate-Union{Tuple{TF}, Tuple{TF, Tuple{Real, Any, TF}}} where TF<:CTFlowsODE.OptimalControlFlow","page":"Concatenation","title":"CTFlowsODE.concatenate","text":"concatenate(\n F::CTFlowsODE.OptimalControlFlow,\n g::Tuple{Real, Any, TF<:CTFlowsODE.OptimalControlFlow}\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConcatenate two OptimalControlFlows and a jump at switching time.\n\nArguments\n\nF::OptimalControlFlow\ng::Tuple{ctNumber,Any,OptimalControlFlow}\n\nReturns\n\nA combined flow with jump and control law switching.\n\nExample\n\njulia> F * (1.0, ฮท, G)\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.concatenate-Union{Tuple{TF}, Tuple{TF, Tuple{Real, TF}}} where TF<:CTFlowsODE.AbstractFlow","page":"Concatenation","title":"CTFlowsODE.concatenate","text":"concatenate(\n F::CTFlowsODE.AbstractFlow,\n g::Tuple{Real, TF<:CTFlowsODE.AbstractFlow}\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConcatenate two AbstractFlow instances with a prescribed switching time.\n\nArguments\n\nF::AbstractFlow: First flow.\ng::Tuple{ctNumber,AbstractFlow}: Switching time and second flow.\n\nReturns\n\nA new flow that transitions from F to G at t_switch.\n\nExample\n\njulia> F * (1.0, G)\n\n\n\n\n\n","category":"method"},{"location":"concatenation.html#CTFlowsODE.concatenate-Union{Tuple{TF}, Tuple{TF, Tuple{Real, TF}}} where TF<:CTFlowsODE.OptimalControlFlow","page":"Concatenation","title":"CTFlowsODE.concatenate","text":"concatenate(\n F::CTFlowsODE.OptimalControlFlow,\n g::Tuple{Real, TF<:CTFlowsODE.OptimalControlFlow}\n) -> Union{CTFlowsODE.OptimalControlFlow{CTFlows.Fixed}, CTFlowsODE.OptimalControlFlow{CTFlows.NonFixed}}\n\n\nConcatenate two OptimalControlFlows at a switching time.\n\nArguments\n\nF::OptimalControlFlow\ng::Tuple{ctNumber,OptimalControlFlow}\n\nReturns\n\nA combined flow with switched dynamics and feedback control.\n\nExample\n\njulia> F * (1.0, G)\n\n\n\n\n\n","category":"method"},{"location":"function.html#Function","page":"Function","title":"Function","text":"","category":"section"},{"location":"function.html#Index","page":"Function","title":"Index","text":"Pages = [\"function.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"function.html#Documentation","page":"Function","title":"Documentation","text":"","category":"section"},{"location":"function.html#CTFlows.Flow-Tuple{Function}","page":"Function","title":"CTFlows.Flow","text":"Flow(\n dyn::Function;\n autonomous,\n variable,\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> CTFlowsODE.ODEFlow\n\n\nConstructs a Flow from a user-defined dynamical system given as a Julia function.\n\nThis high-level interface handles:\n\nautonomous and non-autonomous systems,\npresence or absence of additional variables (v),\nselection of ODE solvers and tolerances,\nand integrates with the CTFlows event system (e.g., jumps, callbacks).\n\nArguments\n\ndyn: A function defining the vector field. Its signature must match the values of autonomous and variable.\nautonomous: Whether the dynamics are time-independent (false by default).\nvariable: Whether the dynamics depend on a control or parameter v.\nalg, abstol, reltol, saveat, internalnorm: Solver settings passed to OrdinaryDiffEq.solve.\nkwargs_Flow: Additional keyword arguments passed to the solver.\n\nReturns\n\nAn ODEFlow object, wrapping both the full solver and its right-hand side (RHS).\n\nSupported Function Signatures for dyn\n\nDepending on the (autonomous, variable) flags:\n\n(false, false): dyn(x)\n(false, true): dyn(x, v)\n(true, false): dyn(t, x)\n(true, true): dyn(t, x, v)\n\nExample\n\njulia> dyn(t, x, v) = [-x[1] + v[1] * sin(t)]\njulia> flow = CTFlows.Flow(dyn; autonomous=true, variable=true)\njulia> xT = flow((0.0, 1.0), [1.0], [0.1])\n\n\n\n\n\n","category":"method"},{"location":"function.html#CTFlowsODE.ode_usage-NTuple{5, Any}","page":"Function","title":"CTFlowsODE.ode_usage","text":"ode_usage(\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm;\n kwargs_Flow...\n) -> Any\n\n\nBuilds a solver function for general ODE problems using OrdinaryDiffEq.solve.\n\nThis utility constructs a reusable solver function that:\n\nhandles optional parameters and control variables,\nintegrates with event-based CallbackSet mechanisms (including jumps),\nsupports both full solutions and one-step propagation,\nmerges solver-specific and global keyword arguments.\n\nReturns\n\nA function f that can be called in two ways:\n\nf(tspan, x0, v=nothing; kwargs...) returns the full ODESolution.\nf(t0, x0, tf, v=nothing; kwargs...) returns only the final state x(tf).\n\nArguments\n\nalg: The numerical integration algorithm (e.g., Tsit5()).\nabstol: Absolute tolerance for the solver.\nreltol: Relative tolerance for the solver.\nsaveat: Optional time steps for solution saving.\ninternalnorm: Norm function used internally for error control.\nkwargs_Flow: Keyword arguments propagated to the solver (unless overridden).\n\nExample\n\njulia> f = ode_usage(Tsit5(), 1e-6, 1e-6, 0.1, InternalNorm())\njulia> sol = f((0.0, 1.0), [1.0, 0.0], [0.0]; jumps=[], _t_stops_interne=[], DiffEqRHS=my_rhs)\n\n\n\n\n\n","category":"method"},{"location":"ctflows.html#CTFlows","page":"CTFlows","title":"CTFlows","text":"","category":"section"},{"location":"ctflows.html#Index","page":"CTFlows","title":"Index","text":"Pages = [\"ctflow.md\"]\nModules = [CTFlows]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"ctflows.html#Documentation","page":"CTFlows","title":"Documentation","text":"","category":"section"},{"location":"hamiltonian.html#Hamiltonian","page":"Hamiltonian","title":"Hamiltonian","text":"","category":"section"},{"location":"hamiltonian.html#Index","page":"Hamiltonian","title":"Index","text":"Pages = [\"hamiltonian.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"hamiltonian.html#Documentation","page":"Hamiltonian","title":"Documentation","text":"","category":"section"},{"location":"hamiltonian.html#CTFlows.Flow-Tuple{CTFlows.AbstractHamiltonian}","page":"Hamiltonian","title":"CTFlows.Flow","text":"Flow(\n h::CTFlows.AbstractHamiltonian;\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> CTFlowsODE.HamiltonianFlow\n\n\nConstructs a Hamiltonian flow from a scalar Hamiltonian.\n\nThis method builds a numerical integrator that simulates the evolution of a Hamiltonian system given a Hamiltonian function h(t, x, p, l) or h(x, p).\n\nInternally, it computes the right-hand side of Hamiltonโ€™s equations via automatic differentiation and returns a HamiltonianFlow object.\n\nKeyword Arguments\n\nalg, abstol, reltol, saveat, internalnorm: solver options.\nkwargs_Flow...: forwarded to the solver.\n\nExample\n\njulia> H(x, p) = dot(p, p) + dot(x, x)\njulia> flow = CTFlows.Flow(CTFlows.Hamiltonian(H))\njulia> xf, pf = flow(0.0, x0, p0, 1.0)\n\n\n\n\n\n","category":"method"},{"location":"hamiltonian.html#CTFlows.Flow-Tuple{CTFlows.HamiltonianVectorField}","page":"Hamiltonian","title":"CTFlows.Flow","text":"Flow(\n hv::CTFlows.HamiltonianVectorField;\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm,\n kwargs_Flow...\n) -> CTFlowsODE.HamiltonianFlow\n\n\nConstructs a Hamiltonian flow from a precomputed Hamiltonian vector field.\n\nThis method assumes you already provide the Hamiltonian vector field (dx/dt, dp/dt) instead of deriving it from a scalar Hamiltonian.\n\nReturns a HamiltonianFlow object that integrates the given system.\n\nKeyword Arguments\n\nalg, abstol, reltol, saveat, internalnorm: solver options.\nkwargs_Flow...: forwarded to the solver.\n\nExample\n\njulia> hv(t, x, p, l) = (โˆ‡โ‚šH, -โˆ‡โ‚“H)\njulia> flow = CTFlows.Flow(CTFlows.HamiltonianVectorField(hv))\njulia> xf, pf = flow(0.0, x0, p0, 1.0, l)\n\n\n\n\n\n","category":"method"},{"location":"hamiltonian.html#CTFlowsODE.hamiltonian_usage-NTuple{5, Any}","page":"Hamiltonian","title":"CTFlowsODE.hamiltonian_usage","text":"hamiltonian_usage(\n alg,\n abstol,\n reltol,\n saveat,\n internalnorm;\n kwargs_Flow...\n) -> Any\n\n\nConstructs a solver function for Hamiltonian systems, with configurable solver options.\n\nReturns a callable object that integrates Hamilton's equations from an initial state (x0, p0) over a time span tspan = (t0, tf), with optional external parameters.\n\nThe returned function has two methods:\n\nf(tspan, x0, p0, v=default_variable; kwargs...) โ†’ returns the full trajectory (solution object).\nf(t0, x0, p0, tf, v=default_variable; kwargs...) โ†’ returns only the final (x, p) state.\n\nInternally, it uses OrdinaryDiffEq.solve and supports events and callbacks.\n\nArguments\n\nalg: integration algorithm (e.g. Tsit5()).\nabstol, reltol: absolute and relative tolerances.\nsaveat: time points for saving.\ninternalnorm: norm used for adaptive integration.\nkwargs_Flow...: additional keyword arguments for the solver.\n\nExample\n\njulia> flowfun = hamiltonian_usage(Tsit5(), 1e-8, 1e-8, 0.1, norm)\njulia> xf, pf = flowfun(0.0, x0, p0, 1.0)\n\n\n\n\n\n","category":"method"},{"location":"hamiltonian.html#CTFlowsODE.rhs-Tuple{CTFlows.AbstractHamiltonian}","page":"Hamiltonian","title":"CTFlowsODE.rhs","text":"rhs(\n h::CTFlows.AbstractHamiltonian\n) -> CTFlowsODE.var\"#rhs!#rhs##0\"{<:CTFlows.AbstractHamiltonian{TD, VD}} where {TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}\n\n\nConstructs the right-hand side of Hamilton's equations from a scalar Hamiltonian function.\n\nGiven a Hamiltonian h(t, x, p, l) (or h(x, p) in the autonomous case), returns an in-place function rhs!(dz, z, v, t) suitable for numerical integration.\n\nThis function computes the canonical Hamiltonian vector field using automatic differentiation:\n\ndz[1:n] = โˆ‚H/โˆ‚p\ndz[n+1:2n] = -โˆ‚H/โˆ‚x\n\nArguments\n\nh: a subtype of CTFlows.AbstractHamiltonian defining the scalar Hamiltonian.\n\nReturns\n\nrhs!: a function for use in an ODE solver.\n\n\n\n\n\n","category":"method"},{"location":"ext_utils.html#Extension-Utils","page":"Extension Utils","title":"Extension Utils","text":"","category":"section"},{"location":"ext_utils.html#Index","page":"Extension Utils","title":"Index","text":"Pages = [\"ext_utils.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"ext_utils.html#Documentation","page":"Extension Utils","title":"Documentation","text":"","category":"section"},{"location":"ext_utils.html#CTFlowsODE.__callbacks-NTuple{5, Any}","page":"Extension Utils","title":"CTFlowsODE.__callbacks","text":"__callbacks(\n callback,\n jumps,\n _rg,\n _t_stops_interne,\n tstops\n) -> Tuple{Any, Any}\n\n\nConstructs the combined callback and stopping times for flow integration.\n\nThis internal utility assembles a CallbackSet for the ODE integrator, handling both:\n\ndiscrete jumps in the state or costate (via VectorContinuousCallback), and\nuser-defined callbacks.\n\nAdditionally, it merges stopping times into a sorted, unique list used for tstops.\n\nArguments\n\ncallback: A user-defined callback (e.g. for logging or monitoring).\njumps: A vector of tuples (t, ฮท) representing discrete updates at time t.\n_rg: An optional index range where the jump ฮท should be applied (e.g. only to p in (x, p)).\n_t_stops_interne: Internal list of event times (mutable, extended in place).\ntstops: Additional stopping times from the outer solver context.\n\nReturns\n\ncb: A CallbackSet combining jumps and user callback.\nt_stops_all: Sorted and deduplicated list of all stopping times.\n\nExample\n\njulia> cb, tstops = __callbacks(mycb, [(1.0, [0.0, -1.0])], 3:4, [], [2.0])\n\n\n\n\n\n","category":"method"},{"location":"index.html#CTFlows.jl","page":"Introduction","title":"CTFlows.jl","text":"The CTFlows.jl package is part of the control-toolbox ecosystem.\n\nThe root package is OptimalControl.jl which aims to provide tools to model and solve optimal control problems with ordinary differential equations by direct and indirect methods, both on CPU and GPU.\n\nAPI Documentation\n\nPages = Main.API_PAGES\nDepth = 1\n\n","category":"section"},{"location":"ext_default.html#Extension-Default","page":"Extension Default","title":"Extension Default","text":"","category":"section"},{"location":"ext_default.html#Index","page":"Extension Default","title":"Index","text":"Pages = [\"ext_default.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"ext_default.html#Documentation","page":"Extension Default","title":"Documentation","text":"","category":"section"},{"location":"ext_default.html#CTFlowsODE.__abstol-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__abstol","text":"__abstol() -> Float64\n\n\nDefault absolute tolerance for ODE solvers.\n\nSee abstol from DifferentialEquations.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__alg-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__alg","text":"__alg(\n\n) -> Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), Static.False}\n\n\nDefault algorithm for ODE solvers.\n\nSee alg from DifferentialEquations.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__callback-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__callback","text":"__callback()\n\n\nSee callback from DifferentialEquations.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__internalnorm-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__internalnorm","text":"__internalnorm(\n\n) -> CTFlowsODE.var\"#__internalnorm##0#__internalnorm##1\"\n\n\nDefault internal norm.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__reltol-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__reltol","text":"__reltol() -> Float64\n\n\nDefault relative tolerance for ODE solvers.\n\nSee reltol from DifferentialEquations.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__saveat-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__saveat","text":"__saveat() -> Vector{Any}\n\n\nSee saveat from DifferentialEquations.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__thevariable-NTuple{5, Any}","page":"Extension Default","title":"CTFlowsODE.__thevariable","text":"__thevariable(t0, x0, p0, tf, ocp) -> Any\n\n\nDefault variable from ocp.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__thevariable-Tuple{Any, Any}","page":"Extension Default","title":"CTFlowsODE.__thevariable","text":"__thevariable(x0, p0) -> Vector\n\n\nDefault variable x0, p0.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__thevariable-Tuple{Any}","page":"Extension Default","title":"CTFlowsODE.__thevariable","text":"__thevariable(x0) -> Vector\n\n\nDefault variable from x0.\n\n\n\n\n\n","category":"method"},{"location":"ext_default.html#CTFlowsODE.__tstops-Tuple{}","page":"Extension Default","title":"CTFlowsODE.__tstops","text":"__tstops() -> Vector{Real}\n\n\nSee tstops from DifferentialEquations.\n\n\n\n\n\n","category":"method"},{"location":"ctflowsode.html#CTFlowsODE","page":"CTFlowsODE","title":"CTFlowsODE","text":"","category":"section"},{"location":"ctflowsode.html#Index","page":"CTFlowsODE","title":"Index","text":"Pages = [\"ctflowsode.md\"]\nModules = [CTFlows, CTFlowsODE]\nOrder = [:module, :constant, :type, :function, :macro]\n\nwarning: Warning\nIn the examples in the documentation below, the methods are not prefixed by the module name even if they are private. julia> using CTFlows\njulia> x = 1\njulia> private_fun(x) # throw an errormust be replaced byjulia> using CTFlows\njulia> x = 1\njulia> CTFlows.private_fun(x)However, if the method is reexported by another package, then, there is no need of prefixing.julia> module OptimalControl\n import CTFlows: private_fun\n export private_fun\n end\njulia> using OptimalControl\njulia> x = 1\njulia> private_fun(x)","category":"section"},{"location":"ctflowsode.html#Documentation","page":"CTFlowsODE","title":"Documentation","text":"","category":"section"},{"location":"ctflowsode.html#CTFlowsODE.CoTangent","page":"CTFlowsODE","title":"CTFlowsODE.CoTangent","text":"Alias for CTFlows.ctVector, representing cotangent vectors in continuous-time systems.\n\nUsed for denoting adjoint states or costates in optimal control formulations.\n\n\n\n\n\n","category":"type"},{"location":"ctflowsode.html#CTFlowsODE.DCoTangent","page":"CTFlowsODE","title":"CTFlowsODE.DCoTangent","text":"Alias for CTFlows.ctVector, representing derivative cotangent vectors.\n\nUseful in contexts where second-order information or directional derivatives of costates are required.\n\n\n\n\n\n","category":"type"},{"location":"ctflowsode.html#CTFlowsODE.AbstractFlow","page":"CTFlowsODE","title":"CTFlowsODE.AbstractFlow","text":"Abstract supertype for continuous-time flows.\n\nAbstractFlow{D,U} defines the interface for any flow system with:\n\nD: the type of the differential (typically a vector or matrix),\nU: the type of the state variable.\n\nSubtypes should define at least a right-hand side function for the system's dynamics.\n\n\n\n\n\n","category":"type"},{"location":"ctflowsode.html#CTFlowsODE.__autonomous","page":"CTFlowsODE","title":"CTFlowsODE.__autonomous","text":"Alias for CTFlows.__autonomous, a tag indicating that a flow is autonomous.\n\nUsed internally to specify behavior in constructors or when composing flows.\n\n\n\n\n\n","category":"function"},{"location":"ctflowsode.html#CTFlowsODE.__create_hamiltonian","page":"CTFlowsODE","title":"CTFlowsODE.__create_hamiltonian","text":"Alias for CTFlows.__create_hamiltonian.\n\nConstructs the Hamiltonian function for a given continuous-time optimal control problem. This internal function typically takes an objective, dynamics, and control constraints.\n\n\n\n\n\n","category":"function"},{"location":"ctflowsode.html#CTFlowsODE.__variable","page":"CTFlowsODE","title":"CTFlowsODE.__variable","text":"Alias for CTFlows.__variable, a tag indicating that a flow depends on external variables or is non-autonomous.\n\nUsed to distinguish time-dependent systems or flows with control/state parameterization.\n\n\n\n\n\n","category":"function"},{"location":"ctflowsode.html#CTFlowsODE.ctgradient","page":"CTFlowsODE","title":"CTFlowsODE.ctgradient","text":"Alias for CTFlows.ctgradient, a method to compute the gradient of a scalar function with respect to a state.\n\nIt dispatches appropriately depending on whether the input is a scalar or a vector, and uses ForwardDiff.jl.\n\n\n\n\n\n","category":"function"},{"location":"ctflowsode.html#CTFlowsODE.rg-Tuple{Int64, Int64}","page":"CTFlowsODE","title":"CTFlowsODE.rg","text":"Creates a range i:j, unless i == j, in which case returns i as an integer.\n\nUseful when indexing or slicing arrays with optional single-element flexibility.\n\n\n\n\n\n","category":"method"}] +} diff --git a/save/docs/build/types.html b/save/docs/build/types.html new file mode 100644 index 00000000..a180cb6e --- /dev/null +++ b/save/docs/build/types.html @@ -0,0 +1,166 @@ + +Types ยท CTFlows.jl

    Types

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.ctVector โ€” Type

    Scalar or vector type used in continuous-time models (scalar or vector-valued).

    source
    CTFlows.Autonomous โ€” Type
    abstract type Autonomous <: CTFlows.TimeDependence

    Indicates the function is autonomous: it does not explicitly depend on time t.

    For example, dynamics of the form f(x, u, p).

    source
    CTFlows.ControlLaw โ€” Type
    struct ControlLaw{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Represents a generic open-loop or closed-loop control law.

    Example

    julia> f(t, x) = -x * exp(-t)
    +julia> u = ControlLaw{typeof(f), NonAutonomous, Fixed}(f)
    +julia> u(1.0, [2.0])
    source
    CTFlows.ControlLaw โ€” Method
    ControlLaw(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.ControlLaw
    +

    Construct a ControlLaw specifying time and variable dependence types.

    source
    CTFlows.ControlLaw โ€” Method
    ControlLaw(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.ControlLaw
    +

    Construct a ControlLaw wrapping the function f.

    Arguments

    • f::Function: The function defining the control law.
    • autonomous::Bool: Whether the system is autonomous.
    • variable::Bool: Whether the function depends on additional variables.

    Returns

    • A ControlLaw instance parameterized accordingly.

    Example

    julia> cl = ControlLaw((x, p) -> -p)
    source
    CTFlows.Dynamics โ€” Type
    struct Dynamics{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Represents the system dynamics dx/dt = f(...).

    Fields

    • f: a callable of the form:
      • f(x, u)
      • f(t, x, u)
      • f(x, u, v)
      • f(t, x, u, v)

    Example

    julia> f(x, u) = x + u
    +julia> dyn = Dynamics{typeof(f), Autonomous, Fixed}(f)
    +julia> dyn([1.0], [2.0])
    source
    CTFlows.Dynamics โ€” Method
    Dynamics(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.Dynamics
    +

    Create a Dynamics object with explicit time and variable dependence.

    Arguments

    • f::Function: The dynamics function.
    • TD: Type indicating time dependence.
    • VD: Type indicating variable dependence.

    Returns

    • A Dynamics{typeof(f),TD,VD} object.
    source
    CTFlows.Dynamics โ€” Method
    Dynamics(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.Dynamics
    +

    Create a Dynamics object representing system dynamics.

    Arguments

    • f::Function: The dynamics function.
    • autonomous::Bool (optional): Whether the dynamics are autonomous (time-independent). Defaults to __autonomous().
    • variable::Bool (optional): Whether the dynamics depend on variables (non-fixed). Defaults to __variable().

    Returns

    • A Dynamics{typeof(f),TD,VD} object.

    Details

    The Dynamics object can be called with various signatures depending on time and variable dependence.

    Examples

    julia> f(x, u) = [x[2], -x[1] + u[1]]
    +julia> dyn = Dynamics(f, autonomous=true, variable=false)
    +julia> dyn([1.0, 0.0], [0.0])  # returns [0.0, -1.0]
    source
    CTFlows.FeedbackControl โ€” Type
    struct FeedbackControl{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Represents a feedback control law: u = f(x) or u = f(t, x).

    Example

    julia> f(x) = -x
    +julia> u = FeedbackControl{typeof(f), Autonomous, Fixed}(f)
    +julia> u([1.0, -1.0])
    source
    CTFlows.FeedbackControl โ€” Method
    FeedbackControl(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.FeedbackControl
    +

    Construct a FeedbackControl specifying time and variable dependence types.

    source
    CTFlows.FeedbackControl โ€” Method
    FeedbackControl(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.FeedbackControl
    +

    Construct a FeedbackControl wrapping the function f.

    Arguments

    • f::Function: The function defining the feedback control.
    • autonomous::Bool: Whether the system is autonomous.
    • variable::Bool: Whether the function depends on additional variables.

    Returns

    • A FeedbackControl instance parameterized accordingly.

    Example

    julia> fb = FeedbackControl(x -> -x)
    source
    CTFlows.Fixed โ€” Type
    abstract type Fixed <: CTFlows.VariableDependence

    Indicates the function has fixed standard arguments only.

    For example, functions of the form f(t, x, p) without any extra variable argument.

    source
    CTFlows.Hamiltonian โ€” Type
    struct Hamiltonian{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Encodes the Hamiltonian function H = โŸจp, fโŸฉ + L in optimal control.

    Fields

    • f: a callable of the form:
      • f(x, p)
      • f(t, x, p)
      • f(x, p, v)
      • f(t, x, p, v)

    Type Parameters

    • TD: Autonomous or NonAutonomous
    • VD: Fixed or NonFixed

    Example

    julia> Hf(x, p) = dot(p, [x[2], -x[1]])
    +julia> H = Hamiltonian{typeof(Hf), Autonomous, Fixed}(Hf)
    +julia> H([1.0, 0.0], [1.0, 1.0])
    source
    CTFlows.Hamiltonian โ€” Method
    Hamiltonian(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.Hamiltonian
    +

    Construct a Hamiltonian function wrapper with explicit time and variable dependence types.

    source
    CTFlows.Hamiltonian โ€” Method
    Hamiltonian(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.Hamiltonian
    +

    Construct a Hamiltonian function wrapper.

    • f: a function representing the Hamiltonian.
    • autonomous: whether f is autonomous (default via __autonomous()).
    • variable: whether f depends on an extra variable argument (default via __variable()).

    Returns a Hamiltonian{TF, TD, VD} callable struct.

    source
    CTFlows.HamiltonianLift โ€” Type
    struct HamiltonianLift{TV<:CTFlows.VectorField, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractHamiltonian{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Lifts a vector field X into a Hamiltonian function using the canonical symplectic structure.

    This is useful to convert a vector field into a Hamiltonian via the identity: H(x, p) = โŸจp, X(x)โŸฉ.

    Constructor

    Use HamiltonianLift(X::VectorField) where X is a VectorField{...}.

    Example

    f(x) = [x[2], -x[1]]
    +julia> X = VectorField{typeof(f), Autonomous, Fixed}(f)
    +julia> H = HamiltonianLift(X)
    +julia> H([1.0, 0.0], [0.5, 0.5])
    source
    CTFlows.HamiltonianLift โ€” Method
    HamiltonianLift(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.HamiltonianLift{CTFlows.VectorField{TF, TD, VD}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}
    +

    Construct a HamiltonianLift with explicit time and variable dependence types.

    source
    CTFlows.HamiltonianLift โ€” Method
    HamiltonianLift(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.HamiltonianLift{CTFlows.VectorField{TF, TD, VD}} where {TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}
    +

    Construct a HamiltonianLift from a vector field function.

    • f: function defining the vector field.
    • autonomous: whether f is autonomous.
    • variable: whether f depends on an extra variable argument.

    Returns a HamiltonianLift wrapping the vector field.

    source
    CTFlows.HamiltonianVectorField โ€” Type
    struct HamiltonianVectorField{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractVectorField{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Represents the Hamiltonian vector field associated to a Hamiltonian function, typically defined as (โˆ‚H/โˆ‚p, -โˆ‚H/โˆ‚x).

    Fields

    • f: a callable implementing the Hamiltonian vector field.

    Example

    julia> f(x, p) = [p[2], -p[1], -x[1], -x[2]]
    +julia> XH = HamiltonianVectorField{typeof(f), Autonomous, Fixed}(f)
    +julia> XH([1.0, 0.0], [0.5, 0.5])
    source
    CTFlows.HamiltonianVectorField โ€” Method
    HamiltonianVectorField(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.HamiltonianVectorField
    +

    Construct a Hamiltonian vector field with explicit time and variable dependence.

    source
    CTFlows.HamiltonianVectorField โ€” Method
    HamiltonianVectorField(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.HamiltonianVectorField
    +

    Construct a Hamiltonian vector field from a function f.

    • autonomous: whether f is autonomous.
    • variable: whether f depends on an extra variable argument.

    Returns a HamiltonianVectorField{TF, TD, VD} callable struct.

    source
    CTFlows.Lagrange โ€” Type
    struct Lagrange{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Encodes the integrand L(t, x, u, ...) of the cost functional in Bolza optimal control problems.

    Fields

    • f: a callable such as:
      • f(x, u)
      • f(t, x, u)
      • f(x, u, v)
      • f(t, x, u, v)

    Example

    julia> L(x, u) = dot(x, x) + dot(u, u)
    +julia> lag = Lagrange{typeof(L), Autonomous, Fixed}(L)
    +julia> lag([1.0, 2.0], [0.5, 0.5])
    source
    CTFlows.Lagrange โ€” Method
    Lagrange(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.Lagrange
    +

    Create a Lagrange object with explicit time and variable dependence.

    Arguments

    • f::Function: The Lagrangian function.
    • TD: Type indicating time dependence.
    • VD: Type indicating variable dependence.

    Returns

    • A Lagrange{typeof(f),TD,VD} object.
    source
    CTFlows.Lagrange โ€” Method
    Lagrange(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.Lagrange
    +

    Create a Lagrange object representing a Lagrangian cost function.

    Arguments

    • f::Function: The Lagrangian function.
    • autonomous::Bool (optional): Whether f is autonomous (time-independent). Defaults to __autonomous().
    • variable::Bool (optional): Whether f depends on variables (non-fixed). Defaults to __variable().

    Returns

    • A Lagrange{typeof(f),TD,VD} object.

    Details

    The Lagrange object can be called with different argument signatures depending on the time and variable dependence.

    Examples

    julia> f(x, u) = sum(abs2, x) + sum(abs2, u)
    +julia> lag = Lagrange(f, autonomous=true, variable=false)
    +julia> lag([1.0, 2.0], [0.5, 0.5])  # returns 5.25
    source
    CTFlows.Mayer โ€” Type
    struct Mayer{TF<:Function, VD<:CTFlows.VariableDependence}

    Encodes the Mayer cost function in optimal control problems.

    This terminal cost term is usually of the form ฯ†(x(tf)) or ฯ†(t, x(tf), v), depending on whether it's autonomous and/or variable-dependent.

    Fields

    • f: a callable of the form:
      • f(x)
      • f(x, v)
      • f(t, x)
      • f(t, x, v) depending on time and variable dependency.

    Example

    julia> ฯ†(x) = norm(x)^2
    +julia> m = Mayer{typeof(ฯ†), Fixed}(ฯ†)
    +julia> m([1.0, 2.0])
    source
    CTFlows.Mayer โ€” Method
    Mayer(
    +    f::Function,
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.Mayer
    +

    Construct a Mayer cost functional wrapper with explicit variable dependence type VD.

    source
    CTFlows.Mayer โ€” Method
    Mayer(
    +    f::Function;
    +    variable
    +) -> Union{CTFlows.Mayer{<:Function, CTFlows.Fixed}, CTFlows.Mayer{<:Function, CTFlows.NonFixed}}
    +

    Construct a Mayer cost functional wrapper.

    • f: a function representing the Mayer cost.
    • variable: whether the function depends on an extra variable argument (default via __variable()).

    Returns a Mayer{TF, VD} callable struct where:

    • TF is the function type
    • VD is either Fixed or NonFixed depending on variable.
    source
    CTFlows.MixedConstraint โ€” Type
    struct MixedConstraint{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Encodes a constraint on both state and control: g(x, u) = 0 or g(t, x, u) = 0.

    Example

    julia> g(x, u) = x[1] + u[1] - 1
    +julia> mc = MixedConstraint{typeof(g), Autonomous, Fixed}(g)
    +julia> mc([0.3], [0.7])
    source
    CTFlows.MixedConstraint โ€” Method
    MixedConstraint(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.MixedConstraint
    +

    Construct a MixedConstraint specifying the time and variable dependence types explicitly.

    source
    CTFlows.MixedConstraint โ€” Method
    MixedConstraint(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.MixedConstraint
    +

    Construct a MixedConstraint object wrapping the function f.

    Arguments

    • f::Function: The function defining the mixed constraint.
    • autonomous::Bool: Whether the system is autonomous.
    • variable::Bool: Whether the function depends on additional variables.

    Returns

    • A MixedConstraint instance parameterized by the type of f and time/variable dependence.

    Example

    julia> mc = MixedConstraint((x, u) -> x + u)
    source
    CTFlows.Multiplier โ€” Type
    struct Multiplier{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Encodes a Lagrange multiplier associated with a constraint.

    Example

    julia> ฮป(t) = [sin(t), cos(t)]
    +julia> ฮผ = Multiplier{typeof(ฮป), NonAutonomous, Fixed}(ฮป)
    +julia> ฮผ(ฯ€ / 2)
    source
    CTFlows.Multiplier โ€” Method
    Multiplier(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.Multiplier
    +

    Construct a Multiplier specifying time and variable dependence types.

    source
    CTFlows.Multiplier โ€” Method
    Multiplier(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.Multiplier
    +

    Construct a Multiplier wrapping the function f.

    Arguments

    • f::Function: The function defining the multiplier.
    • autonomous::Bool: Whether the system is autonomous.
    • variable::Bool: Whether the function depends on additional variables.

    Returns

    • A Multiplier instance parameterized accordingly.

    Example

    julia> m = Multiplier((x, p) -> p .* x)
    source
    CTFlows.NonAutonomous โ€” Type
    abstract type NonAutonomous <: CTFlows.TimeDependence

    Indicates the function is non-autonomous: it explicitly depends on time t.

    For example, dynamics of the form f(t, x, u, p).

    source
    CTFlows.NonFixed โ€” Type
    abstract type NonFixed <: CTFlows.VariableDependence

    Indicates the function has an additional variable argument v.

    For example, functions of the form f(t, x, p, v) where v is a multiplier or auxiliary parameter.

    source
    CTFlows.StateConstraint โ€” Type
    struct StateConstraint{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Encodes a pure state constraint g(x) = 0 or g(t, x) = 0.

    Fields

    • f: a callable depending on time or not, with or without variable dependency.

    Example

    julia> g(x) = x[1]^2 + x[2]^2 - 1
    +julia> c = StateConstraint{typeof(g), Autonomous, Fixed}(g)
    +julia> c([1.0, 0.0])
    source
    CTFlows.StateConstraint โ€” Method
    StateConstraint(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.StateConstraint
    +

    Construct a StateConstraint specifying the time and variable dependence types explicitly.

    Arguments

    • f::Function: The function defining the state constraint.
    • TD::Type: The time dependence type (Autonomous or NonAutonomous).
    • VD::Type: The variable dependence type (Fixed or NonFixed).

    Returns

    • A StateConstraint instance parameterized accordingly.
    source
    CTFlows.StateConstraint โ€” Method
    StateConstraint(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.StateConstraint
    +

    Construct a StateConstraint object wrapping the function f.

    Arguments

    • f::Function: The function defining the state constraint.
    • autonomous::Bool: Whether the system is autonomous (default uses __autonomous()).
    • variable::Bool: Whether the function depends on additional variables (default uses __variable()).

    Returns

    • A StateConstraint instance parameterized by the type of f and time/variable dependence.

    Example

    julia> sc = StateConstraint(x -> x .- 1)  # Autonomous, fixed variable by default
    source
    CTFlows.TimeDependence โ€” Type
    abstract type TimeDependence

    Base abstract type representing the dependence of a function on time.

    Used as a trait to distinguish autonomous vs. non-autonomous functions.

    source
    CTFlows.VariableDependence โ€” Type
    abstract type VariableDependence

    Base abstract type representing whether a function depends on an additional variable argument.

    Used to distinguish fixed-argument functions from those with auxiliary parameters.

    source
    CTFlows.VectorField โ€” Type
    struct VectorField{TF<:Function, TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence} <: CTFlows.AbstractVectorField{TD<:CTFlows.TimeDependence, VD<:CTFlows.VariableDependence}

    Represents a dynamical system dx/dt = f(...) as a vector field.

    Fields

    • f: a callable of the form:
      • f(x)
      • f(t, x)
      • f(x, v)
      • f(t, x, v)

    Example

    f(x) = [x[2], -x[1]]
    +vf = VectorField{typeof(f), Autonomous, Fixed}(f)
    +vf([1.0, 0.0])
    source
    CTFlows.VectorField โ€” Method
    VectorField(
    +    f::Function,
    +    TD::Type{<:CTFlows.TimeDependence},
    +    VD::Type{<:CTFlows.VariableDependence}
    +) -> CTFlows.VectorField
    +

    Create a VectorField object with explicit time and variable dependence types.

    Arguments

    • f::Function: The vector field function.
    • TD: Type indicating time dependence (Autonomous or NonAutonomous).
    • VD: Type indicating variable dependence (Fixed or NonFixed).

    Returns

    • A VectorField{typeof(f),TD,VD} object.
    source
    CTFlows.VectorField โ€” Method
    VectorField(
    +    f::Function;
    +    autonomous,
    +    variable
    +) -> CTFlows.VectorField
    +

    Create a VectorField object wrapping the function f.

    Arguments

    • f::Function: The vector field function.
    • autonomous::Bool (optional): If true, the vector field is autonomous (time-independent). Defaults to __autonomous().
    • variable::Bool (optional): If true, the vector field depends on control or decision variables (non-fixed). Defaults to __variable().

    Returns

    • A VectorField{typeof(f),TD,VD} object where TD encodes time dependence and VD encodes variable dependence.

    Details

    The VectorField object can be called with different argument signatures depending on the time and variable dependence.

    Examples

    julia> f(x) = [-x[2], x[1]]
    +julia> vf = VectorField(f, autonomous=true, variable=false)
    +julia> vf([1.0, 0.0])  # returns [-0.0, 1.0]
    source
    CTFlows.ctNumber โ€” Type

    Base scalar type used in continuous-time models, e.g. Float64 or Dual numbers.

    source
    diff --git a/save/docs/build/utils.html b/save/docs/build/utils.html new file mode 100644 index 00000000..c8f8b003 --- /dev/null +++ b/save/docs/build/utils.html @@ -0,0 +1,27 @@ + +Utils ยท CTFlows.jl

    Utils

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.ctgradient โ€” Function
    ctgradient(f::Function, x::Real) -> Any
    +

    Compute the derivative of a scalar function f at a scalar point x.

    Arguments

    • f::Function: A scalar-valued function.
    • x::ctNumber: A scalar input.

    Returns

    • The derivative of f evaluated at x.

    Example

    julia> ctgradient(x -> x^2, 3.0)  # returns 6.0
    source
    ctgradient(f::Function, x) -> Any
    +

    Compute the gradient of a scalar function f at a vector point x.

    Arguments

    • f::Function: A scalar-valued function accepting a vector input.
    • x: A vector of numbers.

    Returns

    • A vector representing the gradient โˆ‡f(x).

    Example

    julia> ctgradient(x -> sum(x.^2), [1.0, 2.0])  # returns [2.0, 4.0]
    source
    ctgradient(X::CTFlows.VectorField, x) -> Any
    +

    Compute the gradient of a VectorField at a given point.

    Arguments

    • X::VectorField: A vector field object with a callable function X.f.
    • x: A scalar or vector input.

    Returns

    • The derivative or gradient depending on the type of x.

    Example

    julia> X = VectorField(x -> x^2)
    +julia> ctgradient(X, 2.0)  # returns 4.0
    source
    CTFlows.ctjacobian โ€” Function
    ctjacobian(f::Function, x::Real) -> Any
    +

    Compute the Jacobian of a vector-valued function f at a scalar point x.

    Arguments

    • f::Function: A vector-valued function.
    • x::ctNumber: A scalar input.

    Returns

    • A matrix representing the Jacobian Jf(x).

    Example

    julia> f(x) = [sin(x), cos(x)]
    +julia> ctjacobian(f, 0.0)  # returns a 2ร—1 matrix
    source
    ctjacobian(f::Function, x) -> Any
    +

    Compute the Jacobian of a vector-valued function f at a vector point x.

    Arguments

    • f::Function: A vector-valued function.
    • x: A vector input.

    Returns

    • A matrix representing the Jacobian Jf(x).

    Example

    julia> f(x) = [x[1]^2, x[2]^2]
    +julia> ctjacobian(f, [1.0, 2.0])  # returns [2.0 0.0; 0.0 4.0]
    source
    ctjacobian(X::CTFlows.VectorField, x) -> Any
    +

    Compute the Jacobian of a VectorField at a given point.

    Arguments

    • X::VectorField: A vector field object with a callable function X.f.
    • x: A scalar or vector input.

    Returns

    • A matrix representing the Jacobian of X at x.

    Example

    julia> X = VectorField(x -> [x[1]^2, x[2]])
    +julia> ctjacobian(X, [1.0, 3.0])  # returns [2.0 0.0; 0.0 1.0]
    source
    diff --git a/save/docs/build/vector_field.html b/save/docs/build/vector_field.html new file mode 100644 index 00000000..db803150 --- /dev/null +++ b/save/docs/build/vector_field.html @@ -0,0 +1,38 @@ + +Vector Field ยท CTFlows.jl

    Vector Field

    Index

    Warning

    In the examples in the documentation below, the methods are not prefixed by the module name even if they are private.

    julia> using CTFlows
    +julia> x = 1
    +julia> private_fun(x) # throw an error

    must be replaced by

    julia> using CTFlows
    +julia> x = 1
    +julia> CTFlows.private_fun(x)

    However, if the method is reexported by another package, then, there is no need of prefixing.

    julia> module OptimalControl
    +           import CTFlows: private_fun
    +           export private_fun
    +       end
    +julia> using OptimalControl
    +julia> x = 1
    +julia> private_fun(x)

    Documentation

    CTFlows.Flow โ€” Method
    Flow(
    +    vf::CTFlows.VectorField;
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm,
    +    kwargs_Flow...
    +) -> CTFlowsODE.VectorFieldFlow
    +

    Constructs a flow object for a classical (non-Hamiltonian) vector field.

    This creates a VectorFieldFlow that integrates the ODE system dx/dt = vf(t, x, v) using DifferentialEquations.jl. It handles both fixed and parametric dynamics, as well as jump discontinuities and event stopping.

    Keyword Arguments

    • alg, abstol, reltol, saveat, internalnorm: Solver options.
    • kwargs_Flow...: Additional arguments passed to the solver configuration.

    Example

    julia> vf(t, x, v) = -v * x
    +julia> flow = CTFlows.Flow(CTFlows.VectorField(vf))
    +julia> x1 = flow(0.0, 1.0, 1.0)
    source
    CTFlowsODE.vector_field_usage โ€” Method
    vector_field_usage(
    +    alg,
    +    abstol,
    +    reltol,
    +    saveat,
    +    internalnorm;
    +    kwargs_Flow...
    +) -> Any
    +

    Returns a function that solves the ODE associated with a classical vector field.

    This utility creates a flow integrator for systems of the form dx/dt = f(t, x, v), where x is the state and v is an external parameter. It supports integration over a time span as well as direct queries for final state evaluation.

    Two overloads are returned:

    • f(tspan, x0, v=default_variable; kwargs...) returns the full solution trajectory.
    • f(t0, x0, tf, v=default_variable; kwargs...) returns only the final state x(tf).

    Internally uses OrdinaryDiffEq.solve, with support for stopping times and jump discontinuities.

    Arguments

    • alg: Integration algorithm (e.g. Tsit5()).
    • abstol, reltol: Absolute and relative tolerances.
    • saveat: Output time step or vector of times.
    • internalnorm: Norm used for adaptive integration.
    • kwargs_Flow...: Default solver options (overridden by explicit kwargs at call site).

    Example

    julia> vf = (t, x, v) -> -v * x
    +julia> flowfun = vector_field_usage(Tsit5(), 1e-8, 1e-8, 0.1, norm)
    +julia> xf = flowfun(0.0, 1.0, 1.0, 2.0)
    source
    diff --git a/save/docs/make.jl b/save/docs/make.jl new file mode 100644 index 00000000..f3d1af42 --- /dev/null +++ b/save/docs/make.jl @@ -0,0 +1,47 @@ +using Documenter +using DocumenterMermaid +using CTFlows +using CTModels +using OrdinaryDiffEq + +# to add docstrings from external packages +const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +Modules = [CTFlowsODE] +for Module in Modules + isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && + DocMeta.setdocmeta!(Module, :DocTestSetup, :(using $Module); recursive=true) +end + +repo_url = "github.com/control-toolbox/CTFlows.jl" + +API_PAGES = [ + "concatenation.md", + "ctflowsode.md", + "default.md", + "differential_geometry.md", + "ext_default.md", + "ext_types.md", + "ext_utils.md", + "function.md", + "hamiltonian.md", + "optimal_control_problem_utils.md", + "optimal_control_problem.md", + "types.md", + "utils.md", + "vector_field.md", +] + +makedocs(; + sitename="CTFlows.jl", + format=Documenter.HTML(; + repolink="https://" * repo_url, + prettyurls=false, + assets=[ + asset("https://control-toolbox.org/assets/css/documentation.css"), + asset("https://control-toolbox.org/assets/js/documentation.js"), + ], + ), + pages=["Introduction" => "index.md", "API" => API_PAGES], +) + +deploydocs(; repo=repo_url * ".git", devbranch="main") diff --git a/docs/src/concatenation.md b/save/docs/src/concatenation.md similarity index 100% rename from docs/src/concatenation.md rename to save/docs/src/concatenation.md diff --git a/docs/src/ctflows.md b/save/docs/src/ctflows.md similarity index 100% rename from docs/src/ctflows.md rename to save/docs/src/ctflows.md diff --git a/docs/src/ctflowsode.md b/save/docs/src/ctflowsode.md similarity index 100% rename from docs/src/ctflowsode.md rename to save/docs/src/ctflowsode.md diff --git a/docs/src/default.md b/save/docs/src/default.md similarity index 100% rename from docs/src/default.md rename to save/docs/src/default.md diff --git a/docs/src/differential_geometry.md b/save/docs/src/differential_geometry.md similarity index 100% rename from docs/src/differential_geometry.md rename to save/docs/src/differential_geometry.md diff --git a/docs/src/ext_default.md b/save/docs/src/ext_default.md similarity index 100% rename from docs/src/ext_default.md rename to save/docs/src/ext_default.md diff --git a/docs/src/ext_types.md b/save/docs/src/ext_types.md similarity index 100% rename from docs/src/ext_types.md rename to save/docs/src/ext_types.md diff --git a/docs/src/ext_utils.md b/save/docs/src/ext_utils.md similarity index 100% rename from docs/src/ext_utils.md rename to save/docs/src/ext_utils.md diff --git a/docs/src/function.md b/save/docs/src/function.md similarity index 100% rename from docs/src/function.md rename to save/docs/src/function.md diff --git a/docs/src/hamiltonian.md b/save/docs/src/hamiltonian.md similarity index 100% rename from docs/src/hamiltonian.md rename to save/docs/src/hamiltonian.md diff --git a/save/docs/src/index.md b/save/docs/src/index.md new file mode 100644 index 00000000..1948e8aa --- /dev/null +++ b/save/docs/src/index.md @@ -0,0 +1,12 @@ +# CTFlows.jl + +The `CTFlows.jl` package is part of the [control-toolbox ecosystem](https://github.com/control-toolbox). + +The root package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl) which aims to provide tools to model and solve optimal control problems with ordinary differential equations by direct and indirect methods, both on CPU and GPU. + +**API Documentation** + +```@contents +Pages = Main.API_PAGES +Depth = 1 +``` diff --git a/docs/src/optimal_control_problem.md b/save/docs/src/optimal_control_problem.md similarity index 100% rename from docs/src/optimal_control_problem.md rename to save/docs/src/optimal_control_problem.md diff --git a/docs/src/optimal_control_problem_utils.md b/save/docs/src/optimal_control_problem_utils.md similarity index 100% rename from docs/src/optimal_control_problem_utils.md rename to save/docs/src/optimal_control_problem_utils.md diff --git a/docs/src/types.md b/save/docs/src/types.md similarity index 100% rename from docs/src/types.md rename to save/docs/src/types.md diff --git a/docs/src/utils.md b/save/docs/src/utils.md similarity index 100% rename from docs/src/utils.md rename to save/docs/src/utils.md diff --git a/docs/src/vector_field.md b/save/docs/src/vector_field.md similarity index 100% rename from docs/src/vector_field.md rename to save/docs/src/vector_field.md diff --git a/ext/CTFlowsODE.jl b/save/ext/CTFlowsODE.jl similarity index 100% rename from ext/CTFlowsODE.jl rename to save/ext/CTFlowsODE.jl diff --git a/ext/concatenation.jl b/save/ext/concatenation.jl similarity index 100% rename from ext/concatenation.jl rename to save/ext/concatenation.jl diff --git a/ext/ext_default.jl b/save/ext/ext_default.jl similarity index 100% rename from ext/ext_default.jl rename to save/ext/ext_default.jl diff --git a/ext/ext_types.jl b/save/ext/ext_types.jl similarity index 100% rename from ext/ext_types.jl rename to save/ext/ext_types.jl diff --git a/ext/ext_utils.jl b/save/ext/ext_utils.jl similarity index 100% rename from ext/ext_utils.jl rename to save/ext/ext_utils.jl diff --git a/ext/function.jl b/save/ext/function.jl similarity index 100% rename from ext/function.jl rename to save/ext/function.jl diff --git a/ext/hamiltonian.jl b/save/ext/hamiltonian.jl similarity index 100% rename from ext/hamiltonian.jl rename to save/ext/hamiltonian.jl diff --git a/ext/optimal_control_problem.jl b/save/ext/optimal_control_problem.jl similarity index 100% rename from ext/optimal_control_problem.jl rename to save/ext/optimal_control_problem.jl diff --git a/ext/vector_field.jl b/save/ext/vector_field.jl similarity index 100% rename from ext/vector_field.jl rename to save/ext/vector_field.jl diff --git a/save/src/CTFlows.jl b/save/src/CTFlows.jl new file mode 100644 index 00000000..b316c766 --- /dev/null +++ b/save/src/CTFlows.jl @@ -0,0 +1,28 @@ +module CTFlows + +# +using CTBase +using CTModels +using DocStringExtensions +using MLStyle +using MacroTools: @capture, postwalk, striplines +using ForwardDiff: ForwardDiff + +# to be extended +Flow(args...; kwargs...) = throw(CTBase.ExtensionError(:OrdinaryDiffEq)) + +# +include("default.jl") +include("types.jl") +include("utils.jl") +include("differential_geometry.jl") +include("optimal_control_problem_utils.jl") + +# +# export VectorField +# export Hamiltonian +# export HamiltonianLift +# export HamiltonianVectorField +# export Flow + +end diff --git a/src/default.jl b/save/src/default.jl similarity index 100% rename from src/default.jl rename to save/src/default.jl diff --git a/src/differential_geometry.jl b/save/src/differential_geometry.jl similarity index 100% rename from src/differential_geometry.jl rename to save/src/differential_geometry.jl diff --git a/src/optimal_control_problem_utils.jl b/save/src/optimal_control_problem_utils.jl similarity index 100% rename from src/optimal_control_problem_utils.jl rename to save/src/optimal_control_problem_utils.jl diff --git a/src/types.jl b/save/src/types.jl similarity index 100% rename from src/types.jl rename to save/src/types.jl diff --git a/src/utils.jl b/save/src/utils.jl similarity index 100% rename from src/utils.jl rename to save/src/utils.jl diff --git a/test/ResetTest.jl b/save/test/ResetTest.jl similarity index 100% rename from test/ResetTest.jl rename to save/test/ResetTest.jl diff --git a/test/extras/Project.toml b/save/test/extras/Project.toml similarity index 100% rename from test/extras/Project.toml rename to save/test/extras/Project.toml diff --git a/test/extras/jump_state_constraint.jl b/save/test/extras/jump_state_constraint.jl similarity index 100% rename from test/extras/jump_state_constraint.jl rename to save/test/extras/jump_state_constraint.jl diff --git a/test/extras/manual_plot_goddard.jl b/save/test/extras/manual_plot_goddard.jl similarity index 100% rename from test/extras/manual_plot_goddard.jl rename to save/test/extras/manual_plot_goddard.jl diff --git a/test/extras/manual_plot_integrator.jl b/save/test/extras/manual_plot_integrator.jl similarity index 100% rename from test/extras/manual_plot_integrator.jl rename to save/test/extras/manual_plot_integrator.jl diff --git a/test/extras/manual_plot_jumps.jl b/save/test/extras/manual_plot_jumps.jl similarity index 100% rename from test/extras/manual_plot_jumps.jl rename to save/test/extras/manual_plot_jumps.jl diff --git a/test/extras/manual_solve_goddard.jl b/save/test/extras/manual_solve_goddard.jl similarity index 100% rename from test/extras/manual_solve_goddard.jl rename to save/test/extras/manual_solve_goddard.jl diff --git a/test/extras/plot_flow.jl b/save/test/extras/plot_flow.jl similarity index 100% rename from test/extras/plot_flow.jl rename to save/test/extras/plot_flow.jl diff --git a/save/test/runtests.jl b/save/test/runtests.jl new file mode 100644 index 00000000..95057db6 --- /dev/null +++ b/save/test/runtests.jl @@ -0,0 +1,52 @@ +# ============================================================================== +# CTFlows Test Runner +# ============================================================================== +# +# See test/README.md for usage instructions (running specific tests, coverage, etc.) +# +# ============================================================================== + +# Test dependencies +using Test +using CTBase +using CTFlows + +# Trigger loading of optional extensions +const TestRunner = Base.get_extension(CTBase, :TestRunner) + +# Controls nested testset output formatting (used by individual test files) +module TestData +const VERBOSE = true +const SHOWTIMING = true +end +using .TestData: VERBOSE, SHOWTIMING + +# Run tests using the TestRunner extension +CTBase.run_tests(; + args=String.(ARGS), + testset_name="CTFlows tests", + available_tests=("suite/*/test_*",), + filename_builder=name -> Symbol(:test_, name), + funcname_builder=name -> Symbol(:test_, name), + verbose=VERBOSE, + showtiming=SHOWTIMING, + test_dir=@__DIR__, +) + +# If running with coverage enabled, remind the user to run the post-processing script +# because .cov files are flushed at process exit and cannot be cleaned up by this script. +if Base.JLOptions().code_coverage != 0 + println( + """ + +================================================================================ +[CTFlows] Coverage files generated. + +To process them, move them to the coverage/ directory, and generate a report, +please run: + + julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; coverage=true); include("test/coverage.jl")' +================================================================================ +""", + ) +end diff --git a/test/test_aqua.jl b/save/test/test_aqua.jl similarity index 100% rename from test/test_aqua.jl rename to save/test/test_aqua.jl diff --git a/test/test_augmented_flow.jl b/save/test/test_augmented_flow.jl similarity index 100% rename from test/test_augmented_flow.jl rename to save/test/test_augmented_flow.jl diff --git a/test/test_concatenation.jl b/save/test/test_concatenation.jl similarity index 100% rename from test/test_concatenation.jl rename to save/test/test_concatenation.jl diff --git a/test/test_default.jl b/save/test/test_default.jl similarity index 100% rename from test/test_default.jl rename to save/test/test_default.jl diff --git a/test/test_differential_geometry.jl b/save/test/test_differential_geometry.jl similarity index 100% rename from test/test_differential_geometry.jl rename to save/test/test_differential_geometry.jl diff --git a/test/test_flow_function.jl b/save/test/test_flow_function.jl similarity index 100% rename from test/test_flow_function.jl rename to save/test/test_flow_function.jl diff --git a/test/test_flow_hamiltonian.jl b/save/test/test_flow_hamiltonian.jl similarity index 100% rename from test/test_flow_hamiltonian.jl rename to save/test/test_flow_hamiltonian.jl diff --git a/test/test_flow_hamiltonian_vector_field.jl b/save/test/test_flow_hamiltonian_vector_field.jl similarity index 100% rename from test/test_flow_hamiltonian_vector_field.jl rename to save/test/test_flow_hamiltonian_vector_field.jl diff --git a/test/test_flow_vector_field.jl b/save/test/test_flow_vector_field.jl similarity index 100% rename from test/test_flow_vector_field.jl rename to save/test/test_flow_vector_field.jl diff --git a/test/test_optimal_control_problem.jl b/save/test/test_optimal_control_problem.jl similarity index 100% rename from test/test_optimal_control_problem.jl rename to save/test/test_optimal_control_problem.jl diff --git a/test/test_saveat.jl b/save/test/test_saveat.jl similarity index 100% rename from test/test_saveat.jl rename to save/test/test_saveat.jl diff --git a/test/test_types.jl b/save/test/test_types.jl similarity index 100% rename from test/test_types.jl rename to save/test/test_types.jl diff --git a/src/CTFlows.jl b/src/CTFlows.jl index b316c766..abf09c18 100644 --- a/src/CTFlows.jl +++ b/src/CTFlows.jl @@ -1,28 +1,66 @@ +""" + CTFlows + +Flow-based integration and optimal control for CTFlows. + +CTFlows provides a modular architecture for building and integrating systems +using flow-based approaches. The package is organised into specialised submodules; +all public symbols are accessed via qualified paths (e.g., `CTFlows.Systems.AbstractSystem`). + +# Architecture Overview + +CTFlows is organised into specialised submodules: + +- **Core**: Shared utilities and types +- **Data**: Data structures (`VectorField`) with traits +- **Systems**: System types (`AbstractSystem`, `VectorFieldSystem`) with traits +- **Flows**: Flow types (`AbstractFlow`, `Flow`) combining systems with integrators +- **Integrators**: ODE integrator strategies (`AbstractIntegrator`, `SciML`) +- **Solutions**: Solution types (`VectorFieldSolution`) and solution building functions + +All submodules export their public API. The package-level module exports nothing; +access symbols via qualified paths like `CTFlows.Systems.AbstractSystem`. +""" module CTFlows -# -using CTBase -using CTModels -using DocStringExtensions -using MLStyle -using MacroTools: @capture, postwalk, striplines -using ForwardDiff: ForwardDiff - -# to be extended -Flow(args...; kwargs...) = throw(CTBase.ExtensionError(:OrdinaryDiffEq)) - -# -include("default.jl") -include("types.jl") -include("utils.jl") -include("differential_geometry.jl") -include("optimal_control_problem_utils.jl") - -# -# export VectorField -# export Hamiltonian -# export HamiltonianLift -# export HamiltonianVectorField -# export Flow - -end +# ============================================================================== +# Include submodules in topological order +# ============================================================================== + +include(joinpath(@__DIR__, "Traits", "Traits.jl")) +using .Traits + +include(joinpath(@__DIR__, "Configs", "Configs.jl")) +using .Configs + +include(joinpath(@__DIR__, "Common", "Common.jl")) +using .Common + +include(joinpath(@__DIR__, "Data", "Data.jl")) +using .Data + +include(joinpath(@__DIR__, "Differentiation", "Differentiation.jl")) +using .Differentiation + +include(joinpath(@__DIR__, "Systems", "Systems.jl")) +using .Systems + +include(joinpath(@__DIR__, "Integrators", "Integrators.jl")) +using .Integrators + +include(joinpath(@__DIR__, "Solutions", "Solutions.jl")) +using .Solutions + +include(joinpath(@__DIR__, "Flows", "Flows.jl")) +using .Flows + +include(joinpath(@__DIR__, "MultiPhase", "MultiPhase.jl")) +using .MultiPhase + +# ============================================================================== +# No exports at package level +# ============================================================================== + +# Users access symbols via qualified paths: CTFlows.Systems.AbstractSystem, etc. + +end # module CTFlows diff --git a/src/Common/Common.jl b/src/Common/Common.jl new file mode 100644 index 00000000..d6af4c1c --- /dev/null +++ b/src/Common/Common.jl @@ -0,0 +1,54 @@ +""" + Common + +Shared utilities and types for CTFlows. + +This module provides fallback implementations for grid invariance (IND) support: +- `deepvalue(x::Real)` โ€” Base case for extracting primal values +- `real_norm(u::Real, t)` โ€” Base case for internal norm computation + +ForwardDiff-specific implementations are provided in `CTFlowsForwardDiff` when ForwardDiff is loaded. +""" +module Common +# ============================================================================== +# External package imports +# ============================================================================== + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +import CTModels.OCP + +# ============================================================================== +# Sibling imports (temporary - will be removed after full refactoring) +# ============================================================================== + +import ..Traits: Traits +import ..Configs: Configs + +# ============================================================================== +# Includes +# ============================================================================== + +include(joinpath(@__DIR__, "helpers.jl")) +include(joinpath(@__DIR__, "abstract_tag.jl")) +include(joinpath(@__DIR__, "abstract_cache.jl")) +include(joinpath(@__DIR__, "ode_parameters.jl")) +include(joinpath(@__DIR__, "default.jl")) +include(joinpath(@__DIR__, "internal_norm.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractTag +export AbstractCache +# Config types moved to Configs module +# export AbstractConfig, AbstractPointConfig, AbstractTrajectoryConfig, AbstractStateConfig, AbstractHamiltonianConfig, AbstractAugmentedHamiltonianConfig +# export StatePointConfig, StateTrajectoryConfig, HamiltonianPointConfig, HamiltonianTrajectoryConfig, AugmentedHamiltonianPointConfig +# export tspan, initial_condition, initial_state, initial_costate, initial_variable_costate, initial_time, final_time +export NotProvided +export ODEParameters, variable, cache +export __is_autonomous, __is_variable, __variable, __unsafe, __is_inplace, _variable_costate +export deepvalue, real_norm, scalarize + +end # module Common diff --git a/src/Common/abstract_cache.jl b/src/Common/abstract_cache.jl new file mode 100644 index 00000000..ff282eb8 --- /dev/null +++ b/src/Common/abstract_cache.jl @@ -0,0 +1,26 @@ +""" +$(TYPEDEF) + +Abstract base type for automatic differentiation caches. + +Caches store pre-allocated buffers and prepared differentiation plans to avoid +repeated allocation during ODE integration. Concrete cache types are extension-specific +(e.g., `DifferentiationInterfaceCache` from the `CTFlowsDifferentiationInterface` extension). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Common + +julia> isabstracttype(Common.AbstractCache) +true +\`\`\` + +# Notes +- Caches are passed through `ODEParameters` during ODE integration +- The cache field defaults to `nothing` for systems that don't require AD +- Concrete cache types are defined in extensions that provide AD backends +- The RHS closure reads `cache(p)` to access the prepared cache + +See also: [`CTFlows.Common.ODEParameters`](@ref), [`CTFlows.Common.AbstractADTrait`](@ref). +""" +abstract type AbstractCache end diff --git a/src/Common/abstract_tag.jl b/src/Common/abstract_tag.jl new file mode 100644 index 00000000..44b81a0c --- /dev/null +++ b/src/Common/abstract_tag.jl @@ -0,0 +1,36 @@ +""" +$(TYPEDEF) + +Abstract tag type for dispatch-based extension architecture. + +Tag types are used as dispatch markers to differentiate between implementations +provided by different package extensions. This pattern allows CTFlows to define +type stubs in the main package that are activated and implemented only when the +corresponding extension is loaded, avoiding direct dependencies while maintaining +extensibility. + +# Interface Requirements + +Concrete tag subtypes should: +- Be empty structs with no fields (pure markers) +- Be used as dispatch parameters in builder functions +- Correspond to a specific package extension (e.g., SciML, Plots) + +# Example +\`\`\`julia-repl +julia> using CTFlows.Integrators + +julia> SciMLTag <: Common.AbstractTag +true + +julia> # The tag is used as a dispatch parameter: +julia> # build_sciml_integrator(SciMLTag; mode=:strict) routes to the +julia> # CTFlowsSciML implementation when the extension is loaded +\`\`\` + +# Notes +- Tag types have no runtime cost (empty structs) +- They enable conditional compilation via Julia's extension system +- The pattern avoids hard dependencies on optional packages +""" +abstract type AbstractTag end \ No newline at end of file diff --git a/src/Common/default.jl b/src/Common/default.jl new file mode 100644 index 00000000..d71f24e8 --- /dev/null +++ b/src/Common/default.jl @@ -0,0 +1,93 @@ +# ============================================================================= +# Default values for time-dependent object constructors +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Default value for autonomous flag in time-dependent object constructors. + +Returns `true` by default, meaning objects do not explicitly depend on time +unless specified otherwise. +""" +__is_autonomous()::Bool = true + +""" +$(TYPEDSIGNATURES) + +Default value for variable flag in time-dependent object constructors. + +Returns `false` by default, meaning objects have fixed parameters unless +specified otherwise. +""" +__is_variable()::Bool = false + +""" +$(TYPEDEF) + +Sentinel type indicating that a variable parameter was not provided. + +Used as the default value for the `variable` keyword argument in flow calls +to distinguish between "user did not provide a variable" and "user explicitly +passed `nothing`". This enables proper dispatch based on the system's +`VariableDependence` trait. + +# Example +```julia +# Default value for variable parameter +__variable()::NotProvided = NotProvided() +``` + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.NonFixed`](@ref). +""" +struct NotProvided end + +""" +$(TYPEDSIGNATURES) + +Default value for variable parameter in user-facing API calls. + +Returns `NotProvided()` by default, meaning the variable parameter is optional +unless required by the system's trait (e.g., NonFixed systems). +""" +__variable()::NotProvided = NotProvided() + +""" +$(TYPEDSIGNATURES) + +Default value for unsafe flag in integration functions. + +Returns `false` by default, meaning ODE solver retcodes are checked and +failures throw exceptions unless explicitly bypassed. +""" +__unsafe()::Bool = false + +""" +$(TYPEDSIGNATURES) + +Default value for in-place flag in vector field constructors. + +Returns `nothing` by default, meaning mutability is auto-detected from +the function signature unless specified otherwise. + +# Returns +- `Nothing`: The default value for the `is_inplace` parameter. + +See also: [`CTFlows.Data.VectorField`](@ref), [`CTFlows.Data.HamiltonianVectorField`](@ref). +""" +__is_inplace() = nothing + +""" +$(TYPEDSIGNATURES) + +Default value for variable_costate flag in Hamiltonian flow calls. + +Returns `false` by default, meaning the flow does not integrate the augmented +variable costate equation `แน—แตฅ = -โˆ‚H/โˆ‚v` unless explicitly requested. + +# Returns +- `Bool`: The default value for the `variable_costate` parameter. + +See also: [`CTFlows.Configs.AugmentedHamiltonianPointConfig`](@ref). +""" +_variable_costate()::Bool = false diff --git a/src/Common/helpers.jl b/src/Common/helpers.jl new file mode 100644 index 00000000..1d1e815e --- /dev/null +++ b/src/Common/helpers.jl @@ -0,0 +1,5 @@ +function scalarize(x::AbstractVector, ::Number) + @assert length(x) == 1 + return x[1] +end +scalarize(x, ::Any) = x \ No newline at end of file diff --git a/src/Common/internal_norm.jl b/src/Common/internal_norm.jl new file mode 100644 index 00000000..b7f89656 --- /dev/null +++ b/src/Common/internal_norm.jl @@ -0,0 +1,73 @@ +# ============================================================================= +# internal_norm โ€” fallback implementations for grid invariance +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Extract the primal value from a number. Base case for scalar numbers. + +This is a fallback implementation used when AD backends are not loaded. +ForwardDiff-specific implementations are provided in `CTFlowsForwardDiff` for +recursive extraction from nested dual numbers. + +# Arguments +- `x::Number`: A number (e.g., `Float64`, `ComplexF64`). + +# Returns +- `Number`: The input value unchanged for real and complex numbers. + +# Example +```julia-repl +julia> using CTFlows.Common + +julia> deepvalue(3.14) +3.14 + +julia> deepvalue(1.0 + 2.0im) +1.0 + 2.0im +``` + +# Notes +- For `ForwardDiff.Dual` numbers, the more specific method in `CTFlowsForwardDiff` + recursively extracts the primal value via `deepvalue(value(x))`. +- This method serves as the base case for all `Number` subtypes not handled by extensions. + +See also: [`CTFlows.Common.real_norm`](@ref). +""" +deepvalue(x::Number) = x + +""" +$(TYPEDSIGNATURES) + +Compute the internal norm for a scalar number. + +This is a fallback implementation used when AD backends are not loaded. +ForwardDiff-specific implementations are provided in `CTFlowsForwardDiff`. + +# Arguments +- `u::Number`: A scalar number (real or complex). +- `t`: Time parameter (unused but required by SciML interface). + +# Returns +- `Number`: The absolute value for real numbers, or magnitude for complex numbers. + +# Example +```julia-repl +julia> using CTFlows.Common + +julia> real_norm(3.0, 0.0) +3.0 + +julia> real_norm(3.0 + 4.0im, 0.0) +5.0 +``` + +# Notes +- For real numbers, returns `abs(u)`. +- For complex numbers, returns `abs(u)` (the magnitude). +- Used by SciML integrators for step-size control in grid-invariant computations. + +See also: [`CTFlows.Common.deepvalue`](@ref). +""" +real_norm(u::Number, t) = abs(u) diff --git a/src/Common/ode_parameters.jl b/src/Common/ode_parameters.jl new file mode 100644 index 00000000..02c11d93 --- /dev/null +++ b/src/Common/ode_parameters.jl @@ -0,0 +1,137 @@ +""" +$(TYPEDEF) + +Wrapper type for parameters passed through SciML's `p` slot. + +This type formalizes the contract for what transits in the ODE problem's +parameter slot. For CTFlows, the primary content is the variable parameter +for `NonFixed` systems (or `nothing` for `Fixed` systems). For systems with +automatic differentiation support, the cache field stores pre-allocated buffers +and prepared differentiation plans. + +The wrapper makes the contract explicit and extensible โ€” additional fields +can be added later (callbacks, extra data) without breaking existing code. + +# Fields +- `variable::V`: The variable parameter (or `nothing` for `Fixed` systems). +- `cache::C`: The AD cache (or `nothing` for systems without AD support). + +# Constructor Validation + +- `V` can be `Nothing` (for `Fixed` systems) or any concrete type (for `NonFixed`). +- `C` can be `Nothing` (for systems without AD) or a concrete `AbstractCache` subtype. +- No validation is performed at construction โ€” the system's `VariableDependence` + and `ADTrait` determine whether `variable` and `cache` should be used. + +# Example +\`\`\`julia +using CTFlows.Common + +# Fixed system: variable is nothing, cache is nothing +params_fixed = ODEParameters(nothing) + +# NonFixed system: variable is a value, cache is nothing +params_nonfixed = ODEParameters(0.5) + +# WithAD system: variable is a value, cache is a concrete cache +cache = MyCache() +params_with_ad = ODEParameters(0.5, cache) +\`\`\` + +# Notes +- This type is used exclusively by the SciML extension to wrap the variable + before passing it to `ODEProblem`. +- The RHS closure reads `variable(p)` to access the actual variable value. +- The RHS closure reads `cache(p)` to access the prepared AD cache. +- A single-argument constructor `ODEParameters(variable)` defaults `cache` to `nothing` + for backward compatibility. + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.NonFixed`](@ref), +[`CTFlows.Common.AbstractCache`](@ref), [`CTFlows.Traits.AbstractADTrait`](@ref). +""" +struct ODEParameters{V, C<:Union{AbstractCache, Nothing}} + variable::V + cache::C +end + +# Backward compatibility constructor for single-argument calls +function ODEParameters(variable) + return ODEParameters(variable, nothing) +end + +""" +$(TYPEDSIGNATURES) + +Accessor for the cache field of `ODEParameters`. + +Returns the AD cache stored in the `ODEParameters` wrapper. +For systems without AD support, this is `nothing`. For systems with AD support, +this is a concrete `AbstractCache` subtype containing pre-allocated buffers and +prepared differentiation plans. + +# Arguments +- `p::ODEParameters`: The ODEParameters instance. + +# Returns +- The cache value (or `nothing` for systems without AD). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Common + +julia> params_fixed = ODEParameters(nothing) +ODEParameters{Nothing, Nothing}(nothing, nothing) + +julia> cache(params_fixed) +nothing + +julia> params_with_ad = ODEParameters(0.5, MyCache()) +ODEParameters{Float64, MyCache}(0.5, MyCache()) + +julia> cache(params_with_ad) +MyCache() +\`\`\` + +See also: [`CTFlows.Common.ODEParameters`](@ref), [`CTFlows.Common.AbstractCache`](@ref). +""" +function cache(p::ODEParameters) + return p.cache +end + +""" +$(TYPEDSIGNATURES) + +Accessor for the variable field of `ODEParameters`. + +Returns the variable parameter stored in the `ODEParameters` wrapper. +For `Fixed` systems, this is `nothing`. For `NonFixed` systems, this is +the actual variable value (scalar or vector). + +# Arguments +- `p::ODEParameters`: The ODEParameters instance. + +# Returns +- The variable value (or `nothing` for Fixed systems). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Common + +julia> params_fixed = ODEParameters(nothing) +ODEParameters{Nothing}(nothing) + +julia> variable(params_fixed) +nothing + +julia> params_nonfixed = ODEParameters(0.5) +ODEParameters{Float64}(0.5) + +julia> variable(params_nonfixed) +0.5 +\`\`\` + +See also: [`CTFlows.Common.ODEParameters`](@ref), [`CTFlows.Traits.VariableDependence`](@ref). +""" +function variable(p::ODEParameters) + return p.variable +end diff --git a/src/Configs/Configs.jl b/src/Configs/Configs.jl new file mode 100644 index 00000000..8d683af5 --- /dev/null +++ b/src/Configs/Configs.jl @@ -0,0 +1,38 @@ +module Configs + +# ============================================================================== +# External package imports +# ============================================================================== + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions + +# ============================================================================== +# Sibling imports +# ============================================================================== + +import ..Traits: Traits + +# ============================================================================== +# Includes +# ============================================================================== + +include(joinpath(@__DIR__, "abstract.jl")) +include(joinpath(@__DIR__, "interface.jl")) +include(joinpath(@__DIR__, "implementations.jl")) +include(joinpath(@__DIR__, "concrete.jl")) +include(joinpath(@__DIR__, "show.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractConfig, AbstractConfigWithMaC +export AbstractPointConfig, AbstractTrajectoryConfig +export AbstractStateConfig, AbstractHamiltonianConfig, AbstractAugmentedHamiltonianConfig +export StatePointConfig, StateTrajectoryConfig +export HamiltonianPointConfig, HamiltonianTrajectoryConfig, AugmentedHamiltonianPointConfig +export tspan, initial_condition, initial_state, initial_costate, initial_variable_costate +export initial_time, final_time, mode_trait, content_trait + +end # module Configs diff --git a/src/Configs/abstract.jl b/src/Configs/abstract.jl new file mode 100644 index 00000000..c4469736 --- /dev/null +++ b/src/Configs/abstract.jl @@ -0,0 +1,154 @@ +# ============================================================================= +# Abstract Types +# ============================================================================= + +""" +$(TYPEDEF) + +Abstract configuration type for integration problems. + +Marker type for dispatch on configuration objects. Concrete subtypes define +specific integration scenarios (e.g., point-to-point, trajectory, costate). + +The type parameters encode: +- `X0`: Type of the initial condition (scalar `Number` or vector `AbstractVector`) +- `Mode`: Integration mode (`PointTrait` or `TrajectoryTrait`) +- `Content`: Content type (`StateTrait` or `HamiltonianTrait`) + +This enables compile-time dispatch on mode and content without runtime type tests. + +# Interface Requirements + +All subtypes must implement: +- `tspan(config)`: Return the time span as a tuple `(t0, tf)`. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Configs + +julia> StatePointConfig <: Configs.AbstractConfig +true + +julia> StatePointConfig <: Configs.AbstractPointConfig +true + +julia> StatePointConfig <: Configs.AbstractStateConfig +true +\`\`\` + +See also: [`CTFlows.Configs.AbstractPointConfig`](@ref), [`CTFlows.Configs.AbstractTrajectoryConfig`](@ref), [`CTFlows.Configs.AbstractStateConfig`](@ref), [`CTFlows.Configs.AbstractHamiltonianConfig`](@ref). +""" +abstract type AbstractConfig{X0} end + +# ============================================================================= +# Type Aliases for Convenient Dispatch +# ============================================================================= + +abstract type AbstractConfigWithMaC{X0, Mode<:Traits.AbstractModeTrait, Content<:Traits.AbstractContentTrait} <: AbstractConfig{X0} end + +""" +$(TYPEDSIGNATURES) + +Return the mode trait type of a configuration. + +Extracts the `Mode` type parameter from the configuration's parametric type, +enabling trait-based dispatch on the integration mode (point vs trajectory). + +# Arguments +- `::AbstractConfigWithMaC{X0, Mode, Content}`: Any concrete configuration subtype. + +# Returns +- `Type{Mode}`: The mode trait type (e.g., `PointTrait` or `TrajectoryTrait`). + +# Example +\`\`\`julia +config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) +Configs.mode_trait(config) === Traits.PointTrait # true +\`\`\` + +See also: [`CTFlows.Configs.AbstractConfigWithMaC`](@ref), [`CTFlows.Traits.PointTrait`](@ref), [`CTFlows.Traits.TrajectoryTrait`](@ref), [`CTFlows.Configs.content_trait`](@ref). +""" +function mode_trait(::AbstractConfigWithMaC{X0, Mode, Content}) where {X0, Mode, Content} + return Mode +end + +""" +$(TYPEDSIGNATURES) + +Return the content trait type of a configuration. + +Extracts the `Content` type parameter from the configuration's parametric type, +enabling trait-based dispatch on the integration content (state, Hamiltonian, augmented Hamiltonian). + +# Arguments +- `::AbstractConfigWithMaC{X0, Mode, Content}`: Any concrete configuration subtype. + +# Returns +- `Type{Content}`: The content trait type (e.g., `StateTrait`, `HamiltonianTrait`, or `AugmentedHamiltonianTrait`). + +# Example +\`\`\`julia +config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) +Configs.content_trait(config) === Traits.HamiltonianTrait # true +\`\`\` + +See also: [`CTFlows.Configs.AbstractConfigWithMaC`](@ref), [`CTFlows.Traits.StateTrait`](@ref), [`CTFlows.Traits.HamiltonianTrait`](@ref), [`CTFlows.Traits.AugmentedHamiltonianTrait`](@ref), [`CTFlows.Configs.mode_trait`](@ref). +""" +function content_trait(::AbstractConfigWithMaC{X0, Mode, Content}) where {X0, Mode, Content} + return Content +end + +""" +$(TYPEDEF) + +Alias for point integration mode configurations. + +Matches any `AbstractConfig` with `PointTrait` as the mode parameter. +""" +const AbstractPointConfig{X0, C} = AbstractConfigWithMaC{X0, Traits.PointTrait, C} + +""" +$(TYPEDEF) + +Alias for trajectory integration mode configurations. + +Matches any `AbstractConfig` with `TrajectoryTrait` as the mode parameter. +""" +const AbstractTrajectoryConfig{X0, C} = AbstractConfigWithMaC{X0, Traits.TrajectoryTrait, C} + +""" +$(TYPEDEF) + +Alias for state content configurations. + +Matches any `AbstractConfig` with `StateTrait` as the content parameter. +""" +const AbstractStateConfig{X0, M} = AbstractConfigWithMaC{X0, M, Traits.StateTrait} + +""" +$(TYPEDEF) + +Alias for Hamiltonian content configurations. + +Matches any `AbstractConfig` with `HamiltonianTrait` as the content parameter. +""" +const AbstractHamiltonianConfig{X0, M} = AbstractConfigWithMaC{X0, M, Traits.HamiltonianTrait} + +""" +$(TYPEDEF) + +Type alias for augmented Hamiltonian configurations, which include state, costate, and augmented variable initial conditions. + +# Type Parameters +- `X0`: Type of the initial condition (typically a vector concatenating state, costate, and augmented variable). +- `M`: Type of the mutability trait (`InPlace` or `OutOfPlace`). + +# Notes +- Augmented Hamiltonian configurations are used for systems where the Hamiltonian depends on an additional variable (e.g., a control parameter or optimization variable). +- The initial condition typically has the form `vcat(x0, p0, pv0)` where `x0` is the initial state, `p0` is the initial costate, and `pv0` is the initial augmented variable. +- Subtypes [`CTFlows.Configs.AbstractConfigWithMaC`](@ref) with [`CTFlows.Traits.AugmentedHamiltonianTrait`](@ref). +- Used in conjunction with [`CTFlows.Systems.HamiltonianSystem`](@ref) for automatic differentiation-based Hamiltonian integration. + +See also: [`CTFlows.Configs.AbstractHamiltonianConfig`](@ref), [`CTFlows.Traits.AugmentedHamiltonianTrait`](@ref), [`CTFlows.Systems.HamiltonianSystem`](@ref). +""" +const AbstractAugmentedHamiltonianConfig{X0, M} = AbstractConfigWithMaC{X0, M, Traits.AugmentedHamiltonianTrait} diff --git a/src/Configs/concrete.jl b/src/Configs/concrete.jl new file mode 100644 index 00000000..d34f1d97 --- /dev/null +++ b/src/Configs/concrete.jl @@ -0,0 +1,231 @@ +# ============================================================================= +# Concrete configurations +# ============================================================================= + +""" +$(TYPEDEF) + +Configuration for a point-to-point integration problem. + +Defines the initial and final time points along with the initial state for +integration from a single initial condition to a specific final time. + +# Fields +- `t0::T0`: Initial time +- `x0::X0`: Initial state vector +- `tf::TF`: Final time + +# Example +\`\`\`julia-repl +julia> using CTFlows.Configs + +julia> config = StatePointConfig(0.0, [1.0, 0.0], 1.0) +StatePointConfig + t0: 0.0 + x0: [1.0, 0.0] + tf: 1.0 +\`\`\` + +See also: [`CTFlows.Configs.StateTrajectoryConfig`](@ref) +""" +struct StatePointConfig{T0<:Real, X0, TF<:Real} <: AbstractConfigWithMaC{X0, Traits.PointTrait, Traits.StateTrait} + t0::T0 + x0::X0 + tf::TF +end + +""" +$(TYPEDEF) + +Configuration for a trajectory integration problem. + +Defines a time span and initial state for integration over a continuous +time interval, useful for generating full trajectories. + +# Fields +- `tspan::TS`: Time span as a tuple (t0, tf) +- `x0::X0`: Initial state vector + +# Example +\`\`\`julia-repl +julia> using CTFlows.Configs + +julia> config = StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) +StateTrajectoryConfig + tspan: (0.0, 1.0) + x0: [1.0, 0.0] +\`\`\` + +See also: [`CTFlows.Configs.StatePointConfig`](@ref) +""" +struct StateTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0} <: AbstractConfigWithMaC{X0, Traits.TrajectoryTrait, Traits.StateTrait} + tspan::TS + x0::X0 +end + + +""" +$(TYPEDEF) + +Configuration for a Hamiltonian point-to-point integration problem. + +Defines the initial and final time points along with the initial state and costate +for integration from a single initial condition to a specific final time in the +Hamiltonian framework. + +# Fields +- `t0::T0`: Initial time +- `x0::X0`: Initial state vector +- `p0::P0`: Initial costate vector +- `tf::TF`: Final time + +# Example +\`\`\`julia-repl +julia> using CTFlows.Configs + +julia> config = HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) +HamiltonianPointConfig + t0: 0.0 + x0: [1.0, 0.0] + p0: [0.5, 0.3] + tf: 1.0 +\`\`\` + +See also: [`CTFlows.Configs.HamiltonianTrajectoryConfig`](@ref), [`CTFlows.Configs.StatePointConfig`](@ref). +""" +struct HamiltonianPointConfig{T0<:Real, X0, P0, TF<:Real} <: AbstractConfigWithMaC{X0, Traits.PointTrait, Traits.HamiltonianTrait} + t0::T0 + x0::X0 + p0::P0 + tf::TF +end + +""" +$(TYPEDEF) + +Configuration for a Hamiltonian trajectory integration problem. + +Defines a time span and initial state and costate for integration over a +continuous time interval in the Hamiltonian framework, useful for generating +full Hamiltonian trajectories. + +# Fields +- `tspan::TS`: Time span as a tuple (t0, tf) +- `x0::X0`: Initial state vector +- `p0::P0`: Initial costate vector + +# Example +\`\`\`julia-repl +julia> using CTFlows.Configs + +julia> config = HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 0.0], [0.5, 0.3]) +HamiltonianTrajectoryConfig + tspan: (0.0, 1.0) + x0: [1.0, 0.0] + p0: [0.5, 0.3] +\`\`\` + +See also: [`CTFlows.Configs.HamiltonianPointConfig`](@ref), [`CTFlows.Configs.StateTrajectoryConfig`](@ref). +""" +struct HamiltonianTrajectoryConfig{TS<:Tuple{<:Real,<:Real}, X0, P0} <: AbstractConfigWithMaC{X0, Traits.TrajectoryTrait, Traits.HamiltonianTrait} + tspan::TS + x0::X0 + p0::P0 +end + +""" +$(TYPEDEF) + +Configuration for an augmented Hamiltonian point-to-point integration problem. + +Defines the initial and final time points along with the initial state, costate, +and variable costate for integration from a single initial condition to a specific +final time in the augmented Hamiltonian framework. + +# Fields +- `t0::T0`: Initial time +- `x0::X0`: Initial state vector +- `p0::P0`: Initial costate vector +- `pv0::PV0`: Initial variable costate vector (typically zeros) +- `tf::TF`: Final time + +# Example +\`\`\`julia-repl +julia> using CTFlows.Configs + +julia> config = AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) +AugmentedHamiltonianPointConfig + t0: 0.0 + x0: [1.0, 0.0] + p0: [0.5, 0.3] + pv0: [0.0, 0.0] + tf: 1.0 +\`\`\` + +See also: [`CTFlows.Configs.HamiltonianPointConfig`](@ref), [`CTFlows.Traits.AugmentedHamiltonianTrait`](@ref). +""" +struct AugmentedHamiltonianPointConfig{T0<:Real, X0, P0, PV0, TF<:Real} <: AbstractAugmentedHamiltonianConfig{X0, Traits.PointTrait} + t0::T0 + x0::X0 + p0::P0 + pv0::PV0 + tf::TF +end + +""" +$(TYPEDSIGNATURES) + +Return the initial condition for augmented Hamiltonian configurations. + +For augmented Hamiltonian systems, the initial condition is the concatenation of the +initial state, initial costate, and initial variable costate: `vcat(x0, p0, pv0)`. + +# Arguments +- `c::AugmentedHamiltonianPointConfig`: The augmented Hamiltonian configuration. + +# Returns +- Concatenated vector `[x0; p0; pv0]`. + +See also: [`CTFlows.Configs.AugmentedHamiltonianPointConfig`](@ref), [`CTFlows.Configs.initial_state`](@ref), [`CTFlows.Configs.initial_costate`](@ref), [`CTFlows.Configs.initial_variable_costate`](@ref). +""" +function initial_condition(c::AugmentedHamiltonianPointConfig) + return vcat(c.x0, c.p0, c.pv0) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial costate for augmented Hamiltonian configurations. + +Extracts the initial costate field from the augmented Hamiltonian configuration. + +# Arguments +- `c::AugmentedHamiltonianPointConfig`: The augmented Hamiltonian configuration. + +# Returns +- The initial costate vector. + +See also: [`CTFlows.Configs.AugmentedHamiltonianPointConfig`](@ref). +""" +function initial_costate(c::AugmentedHamiltonianPointConfig) + return c.p0 +end + +""" +$(TYPEDSIGNATURES) + +Return the initial variable costate for augmented Hamiltonian configurations. + +Extracts the initial variable costate field from the augmented Hamiltonian configuration. + +# Arguments +- `c::AugmentedHamiltonianPointConfig`: The augmented Hamiltonian configuration. + +# Returns +- The initial variable costate vector. + +See also: [`CTFlows.Configs.AugmentedHamiltonianPointConfig`](@ref). +""" +function initial_variable_costate(c::AugmentedHamiltonianPointConfig) + return c.pv0 +end diff --git a/src/Configs/implementations.jl b/src/Configs/implementations.jl new file mode 100644 index 00000000..7c32f5b7 --- /dev/null +++ b/src/Configs/implementations.jl @@ -0,0 +1,241 @@ +# ============================================================================= +# Interface implementations on abstract config types +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return the time span as a tuple for point configurations. + +For point configurations, extracts the initial and final times from the +`t0` and `tf` fields. + +# Arguments +- `c::AbstractPointConfig`: The point configuration. + +# Returns +- `Tuple{Real, Real}`: Time span as (t0, tf). + +See also: [`CTFlows.Configs.AbstractPointConfig`](@ref), [`CTFlows.Configs.tspan`](@ref). +""" +function tspan(c::AbstractPointConfig)::Tuple{Real, Real} + return (c.t0, c.tf) +end + +""" +$(TYPEDSIGNATURES) + +Return the time span for trajectory configurations. + +For trajectory configurations, returns the stored `tspan` field directly. + +# Arguments +- `c::AbstractTrajectoryConfig`: The trajectory configuration. + +# Returns +- `Tuple{Real, Real}`: Time span as (t0, tf). + +See also: [`CTFlows.Configs.AbstractTrajectoryConfig`](@ref), [`CTFlows.Configs.tspan`](@ref). +""" +function tspan(c::AbstractTrajectoryConfig)::Tuple{Real, Real} + return c.tspan +end + +""" +$(TYPEDSIGNATURES) + +Return the initial time for point configurations. + +For point configurations, returns the stored `t0` field directly. + +# Arguments +- `c::AbstractPointConfig`: The point configuration. + +# Returns +- `Real`: Initial time t0. + +See also: [`CTFlows.Configs.AbstractPointConfig`](@ref), [`CTFlows.Configs.initial_time`](@ref). +""" +function initial_time(c::AbstractPointConfig)::Real + return c.t0 +end + +""" +$(TYPEDSIGNATURES) + +Return the initial time for trajectory configurations. + +For trajectory configurations, returns the first element of the stored `tspan` field. + +# Arguments +- `c::AbstractTrajectoryConfig`: The trajectory configuration. + +# Returns +- `Real`: Initial time t0. + +See also: [`CTFlows.Configs.AbstractTrajectoryConfig`](@ref), [`CTFlows.Configs.initial_time`](@ref). +""" +function initial_time(c::AbstractTrajectoryConfig)::Real + return c.tspan[1] +end + +""" +$(TYPEDSIGNATURES) + +Return the final time for point configurations. + +For point configurations, returns the stored `tf` field directly. + +# Arguments +- `c::AbstractPointConfig`: The point configuration. + +# Returns +- `Real`: Final time tf. + +See also: [`CTFlows.Configs.AbstractPointConfig`](@ref), [`CTFlows.Configs.final_time`](@ref). +""" +function final_time(c::AbstractPointConfig)::Real + return c.tf +end + +""" +$(TYPEDSIGNATURES) + +Return the final time for trajectory configurations. + +For trajectory configurations, returns the second element of the stored `tspan` field. + +# Arguments +- `c::AbstractTrajectoryConfig`: The trajectory configuration. + +# Returns +- `Real`: Final time tf. + +See also: [`CTFlows.Configs.AbstractTrajectoryConfig`](@ref), [`CTFlows.Configs.final_time`](@ref). +""" +function final_time(c::AbstractTrajectoryConfig)::Real + return c.tspan[2] +end + +""" +$(TYPEDSIGNATURES) + +Return the initial condition as a vector for scalar state configurations. + +For scalar initial conditions, wraps the scalar in a length-1 vector to +maintain consistent vector-based ODE problem construction. + +# Arguments +- `c::AbstractStateConfig{<:Number, M}`: The state configuration with scalar initial state. + +# Returns +- `Vector{<:Number}`: Length-1 vector containing the scalar initial state. + +See also: [`CTFlows.Configs.AbstractStateConfig`](@ref), [`CTFlows.Configs.initial_condition`](@ref). +""" +function initial_condition(c::AbstractStateConfig{<:Number, M}) where {M} + return [c.x0] +end + +""" +$(TYPEDSIGNATURES) + +Return the initial condition for state configurations. + +For vector initial conditions, returns the state vector directly. + +# Arguments +- `c::AbstractStateConfig`: The state configuration. + +# Returns +- The initial state vector. + +See also: [`CTFlows.Configs.AbstractStateConfig`](@ref). +""" +function initial_condition(c::AbstractStateConfig) + return c.x0 +end + +""" +$(TYPEDSIGNATURES) + +Return the initial condition for Hamiltonian configurations. + +For Hamiltonian systems, the initial condition is the concatenation of the +initial state and initial costate: `vcat(x0, p0)`. + +# Arguments +- `c::AbstractHamiltonianConfig`: The Hamiltonian configuration. + +# Returns +- Concatenated vector `[x0; p0]`. + +See also: [`CTFlows.Configs.AbstractHamiltonianConfig`](@ref), [`CTFlows.Configs.initial_state`](@ref), [`CTFlows.Configs.initial_costate`](@ref). +""" +function initial_condition(c::AbstractHamiltonianConfig) + return vcat(c.x0, c.p0) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial state from a configuration. + +Extracts the initial state field from the configuration. + +# Arguments +- `c::AbstractConfigWithMaC`: The configuration with mode and content traits. + +# Returns +- The initial state vector. + +See also: [`CTFlows.Configs.AbstractConfigWithMaC`](@ref). +""" +function initial_state(c::AbstractConfigWithMaC) + return c.x0 +end + +""" +$(TYPEDSIGNATURES) + +Return the initial costate for state configurations (error stub). + +State configurations do not have a costate field. This method throws a +`PreconditionError` to enforce the contract that `initial_costate` is only +defined for Hamiltonian configurations. + +# Arguments +- `c::AbstractStateConfig`: The state configuration. + +# Throws +- `Exceptions.PreconditionError`: Always thrown for state configurations. + +See also: [`CTFlows.Configs.AbstractStateConfig`](@ref), [`CTFlows.Configs.AbstractHamiltonianConfig`](@ref), [`CTFlows.Configs.initial_costate`](@ref). +""" +function initial_costate(c::AbstractStateConfig) + throw(Exceptions.PreconditionError( + "initial_costate is only defined for Hamiltonian configs"; + context = "initial_costate - requires Hamiltonian config", + reason = "config type $(typeof(c)) does not have a costate field", + suggestion = "use HamiltonianPointConfig or HamiltonianTrajectoryConfig instead", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial costate for Hamiltonian configurations. + +Extracts the initial costate field from the Hamiltonian configuration. + +# Arguments +- `c::AbstractHamiltonianConfig`: The Hamiltonian configuration. + +# Returns +- The initial costate vector. + +See also: [`CTFlows.Configs.AbstractHamiltonianConfig`](@ref). +""" +function initial_costate(c::AbstractHamiltonianConfig) + return c.p0 +end diff --git a/src/Configs/interface.jl b/src/Configs/interface.jl new file mode 100644 index 00000000..ff37d642 --- /dev/null +++ b/src/Configs/interface.jl @@ -0,0 +1,151 @@ +# ============================================================================= +# Interface: stubs +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return the time span for a configuration (stub method). + +This is a stub method on the base `AbstractConfig` type that throws +`NotImplemented`. Concrete subtypes should implement this method to return +their specific time span format. + +# Arguments +- `c::AbstractConfig`: The configuration. + +# Throws +- `Exceptions.NotImplemented`: Always thrown for the base abstract type. + +See also: [`CTFlows.Configs.AbstractConfig`](@ref). +""" +function tspan(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig tspan method not implemented"; + required_method = "tspan(c::$(typeof(c)))", + suggestion = "Return the time span as a tuple (t0, tf) for this configuration.", + context = "AbstractConfig.tspan - required method implementation", + )) +end + +function initial_time(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig initial_time method not implemented"; + required_method = "initial_time(c::$(typeof(c)))", + suggestion = "Return the initial time for this configuration.", + context = "AbstractConfig.initial_time - required method implementation", + )) +end + +function final_time(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig final_time method not implemented"; + required_method = "final_time(c::$(typeof(c)))", + suggestion = "Return the final time for this configuration.", + context = "AbstractConfig.final_time - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial condition for a configuration (stub method). + +This is a stub method on the base `AbstractConfig` type that throws +`NotImplemented`. Concrete subtypes should implement this method to return +their specific initial condition format. + +# Arguments +- `c::AbstractConfig`: The configuration. + +# Throws +- `Exceptions.NotImplemented`: Always thrown for the base abstract type. + +See also: [`CTFlows.Configs.AbstractConfig`](@ref). +""" +function initial_condition(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig initial_condition method not implemented"; + required_method = "initial_condition(c::$(typeof(c)))", + suggestion = "Return the initial condition for this configuration.", + context = "AbstractConfig.initial_condition - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial state for a configuration (stub method). + +This is a stub method on the base `AbstractConfig` type that throws +`NotImplemented`. Concrete subtypes should implement this method to return +their specific initial state. + +# Arguments +- `c::AbstractConfig`: The configuration. + +# Throws +- `Exceptions.NotImplemented`: Always thrown for the base abstract type. + +See also: [`CTFlows.Configs.AbstractConfig`](@ref). +""" +function initial_state(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig initial_state method not implemented"; + required_method = "initial_state(c::$(typeof(c)))", + suggestion = "Return the initial state for this configuration.", + context = "AbstractConfig.initial_state - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial costate for a configuration (stub method). + +This is a stub method on the base `AbstractConfig` type that throws +`NotImplemented`. Concrete subtypes should implement this method to return +their specific initial costate (if applicable). + +# Arguments +- `c::AbstractConfig`: The configuration. + +# Throws +- `Exceptions.NotImplemented`: Always thrown for the base abstract type. + +See also: [`CTFlows.Configs.AbstractConfig`](@ref). +""" +function initial_costate(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig initial_costate method not implemented"; + required_method = "initial_costate(c::$(typeof(c)))", + suggestion = "Return the initial costate for this configuration.", + context = "AbstractConfig.initial_costate - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the initial variable costate for a configuration (stub method). + +This is a stub method on the base `AbstractConfig` type that throws +`NotImplemented`. Concrete subtypes should implement this method to return +their specific initial variable costate (if applicable). + +# Arguments +- `c::AbstractConfig`: The configuration. + +# Throws +- `Exceptions.NotImplemented`: Always thrown for the base abstract type. + +See also: [`CTFlows.Configs.AbstractConfig`](@ref). +""" +function initial_variable_costate(c::AbstractConfig) + throw(Exceptions.NotImplemented( + "AbstractConfig initial_variable_costate method not implemented"; + required_method = "initial_variable_costate(c::$(typeof(c)))", + suggestion = "Return the initial variable costate for this configuration.", + context = "AbstractConfig.initial_variable_costate - required method implementation", + )) +end diff --git a/src/Configs/show.jl b/src/Configs/show.jl new file mode 100644 index 00000000..53e1b007 --- /dev/null +++ b/src/Configs/show.jl @@ -0,0 +1,110 @@ +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display the `StatePointConfig` in tree-style format. +""" +function Base.show(io::IO, c::StatePointConfig) + println(io, "StatePointConfig") + println(io, " t0: ", c.t0) + println(io, " x0: ", c.x0) + print(io, " tf: ", c.tf) +end + +""" +$(TYPEDSIGNATURES) + +Display the `StatePointConfig` in REPL format. +""" +function Base.show(io::IO, ::MIME"text/plain", c::StatePointConfig) + show(io, c) +end + +""" +$(TYPEDSIGNATURES) + +Display the `StateTrajectoryConfig` in tree-style format. +""" +function Base.show(io::IO, c::StateTrajectoryConfig) + println(io, "StateTrajectoryConfig") + println(io, " tspan: ", c.tspan) + print(io, " x0: ", c.x0) +end + +""" +$(TYPEDSIGNATURES) + +Display the `StateTrajectoryConfig` in REPL format. +""" +function Base.show(io::IO, ::MIME"text/plain", c::StateTrajectoryConfig) + show(io, c) +end + +""" +$(TYPEDSIGNATURES) + +Display the `HamiltonianPointConfig` in tree-style format. +""" +function Base.show(io::IO, c::HamiltonianPointConfig) + println(io, "HamiltonianPointConfig") + println(io, " t0: ", c.t0) + println(io, " x0: ", c.x0) + println(io, " p0: ", c.p0) + print(io, " tf: ", c.tf) +end + +""" +$(TYPEDSIGNATURES) + +Display the `HamiltonianPointConfig` in REPL format. +""" +function Base.show(io::IO, ::MIME"text/plain", c::HamiltonianPointConfig) + show(io, c) +end + +""" +$(TYPEDSIGNATURES) + +Display the `HamiltonianTrajectoryConfig` in tree-style format. +""" +function Base.show(io::IO, c::HamiltonianTrajectoryConfig) + println(io, "HamiltonianTrajectoryConfig") + println(io, " tspan: ", c.tspan) + println(io, " x0: ", c.x0) + print(io, " p0: ", c.p0) +end + +""" +$(TYPEDSIGNATURES) + +Display the `HamiltonianTrajectoryConfig` in REPL format. +""" +function Base.show(io::IO, ::MIME"text/plain", c::HamiltonianTrajectoryConfig) + show(io, c) +end + +""" +$(TYPEDSIGNATURES) + +Display the `AugmentedHamiltonianPointConfig` in tree-style format. +""" +function Base.show(io::IO, c::AugmentedHamiltonianPointConfig) + println(io, "AugmentedHamiltonianPointConfig") + println(io, " t0: ", c.t0) + println(io, " x0: ", c.x0) + println(io, " p0: ", c.p0) + println(io, " pv0: ", c.pv0) + print(io, " tf: ", c.tf) +end + +""" +$(TYPEDSIGNATURES) + +Display the `AugmentedHamiltonianPointConfig` in REPL format. +""" +function Base.show(io::IO, ::MIME"text/plain", c::AugmentedHamiltonianPointConfig) + show(io, c) +end diff --git a/src/Data/Data.jl b/src/Data/Data.jl new file mode 100644 index 00000000..9ec3ee3e --- /dev/null +++ b/src/Data/Data.jl @@ -0,0 +1,43 @@ +""" + Data + +Data structures for CTFlows including vector fields and Hamiltonian vector fields with traits. + +This module defines the `VectorField` and `HamiltonianVectorField` types which encapsulate +vector-field functions together with their time-dependence and variable-dependence traits. +""" +module Data + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions + +# ============================================================================== +# Internal sibling-submodule imports +# ============================================================================== + +import ..Common: Common +import ..Traits: Traits + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "helpers.jl")) +include(joinpath(@__DIR__, "abstract_vector_field.jl")) +include(joinpath(@__DIR__, "vector_field.jl")) +include(joinpath(@__DIR__, "abstract_hamiltonian.jl")) +include(joinpath(@__DIR__, "hamiltonian.jl")) +include(joinpath(@__DIR__, "hamiltonian_vector_field.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractVectorField +export VectorField +export HamiltonianVectorField +export AbstractHamiltonian +export Hamiltonian + +end # module Data diff --git a/src/Data/abstract_hamiltonian.jl b/src/Data/abstract_hamiltonian.jl new file mode 100644 index 00000000..c728c095 --- /dev/null +++ b/src/Data/abstract_hamiltonian.jl @@ -0,0 +1,91 @@ +""" +$(TYPEDEF) + +Abstract supertype for scalar Hamiltonian functions together with their +time-dependence and variable-dependence traits. + +A Hamiltonian is a scalar function `H(t, x, p[, v]) โ†’ โ„` from which a +Hamiltonian vector field can be derived via automatic differentiation. +Unlike vector fields, a Hamiltonian has no mutability trait (in-place vs +out-of-place) because a scalar return has no meaningful in-place form. + +# Type Parameters +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. + +# Notes +- All Hamiltonian types support both natural and uniform call signatures. +- The uniform signature `(t, x, p, v)` is used internally by systems. + +See also: [`CTFlows.Data.Hamiltonian`](@ref), [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.VariableDependence`](@ref). +""" +abstract type AbstractHamiltonian{ + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence +} end + +# ============================================================================= +# Trait accessors for AbstractHamiltonian +# ============================================================================= + +""" + Traits.has_time_dependence_trait(::AbstractHamiltonian) -> true + +Indicates that all `AbstractHamiltonian` types support time-dependence queries. + +# Returns +- `true`: Always returns `true` for Hamiltonian types. + +See also: [`CTFlows.Traits.time_dependence`](@ref), [`CTFlows.Data.AbstractHamiltonian`](@ref). +""" +function Traits.has_time_dependence_trait(::AbstractHamiltonian) + return true +end + +""" + Traits.has_variable_dependence_trait(::AbstractHamiltonian) -> true + +Indicates that all `AbstractHamiltonian` types support variable-dependence queries. + +# Returns +- `true`: Always returns `true` for Hamiltonian types. + +See also: [`CTFlows.Traits.variable_dependence`](@ref), [`CTFlows.Data.AbstractHamiltonian`](@ref). +""" +function Traits.has_variable_dependence_trait(::AbstractHamiltonian) + return true +end + +""" + Traits.time_dependence(h::AbstractHamiltonian{TD, VD}) where {TD, VD} -> TD + +Return the time-dependence trait of a Hamiltonian. + +# Arguments +- `h::AbstractHamiltonian`: The Hamiltonian object. + +# Returns +- `TD`: The time-dependence type (`Autonomous` or `NonAutonomous`). + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.Autonomous`](@ref), [`CTFlows.Traits.NonAutonomous`](@ref). +""" +function Traits.time_dependence(::AbstractHamiltonian{TD, <:Traits.VariableDependence}) where {TD <: Traits.TimeDependence} + return TD +end + +""" + Traits.variable_dependence(h::AbstractHamiltonian{TD, VD}) where {TD, VD} -> VD + +Return the variable-dependence trait of a Hamiltonian. + +# Arguments +- `h::AbstractHamiltonian`: The Hamiltonian object. + +# Returns +- `VD`: The variable-dependence type (`Fixed` or `NonFixed`). + +See also: [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.NonFixed`](@ref). +""" +function Traits.variable_dependence(::AbstractHamiltonian{<:Traits.TimeDependence, VD}) where {VD <: Traits.VariableDependence} + return VD +end diff --git a/src/Data/abstract_vector_field.jl b/src/Data/abstract_vector_field.jl new file mode 100644 index 00000000..e6502998 --- /dev/null +++ b/src/Data/abstract_vector_field.jl @@ -0,0 +1,162 @@ +""" +$(TYPEDEF) + +Abstract type for all vector fields in CTFlows. + +An `AbstractVectorField` represents a vector field function with time-dependence, +variable-dependence, and mutability traits encoded in the type parameters. + +# Contract + +All subtypes must have type parameters: +- `TD <: Traits.TimeDependence`: `Autonomous` or `NonAutonomous` +- `VD <: Traits.VariableDependence`: `Fixed` or `NonFixed` +- `MD <: Traits.AbstractMutabilityTrait`: `InPlace` or `OutOfPlace` + +Trait accessors are implemented at the abstract level and work for all subtypes. + +# Example + +\`\`\`julia +using CTFlows.Data +using CTFlows.Common + +# Define a concrete vector field +struct MyVectorField{F, TD, VD, MD} <: AbstractVectorField{TD, VD, MD} + f::F +end + +# Trait accessors work automatically +vf = MyVectorField(x -> -x, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) +Traits.time_dependence(vf) # Returns Autonomous +Traits.variable_dependence(vf) # Returns Fixed +Traits.mutability_trait(vf) # Returns OutOfPlace +\`\`\` + +See also: [`CTFlows.Data.VectorField`](@ref), [`CTFlows.Data.HamiltonianVectorField`](@ref), [`CTFlows.Traits.time_dependence`](@ref), [`CTFlows.Traits.variable_dependence`](@ref), [`CTFlows.Traits.mutability_trait`](@ref). +""" +abstract type AbstractVectorField{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} end + +# ============================================================================= +# Trait accessors for AbstractVectorField +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractVectorField` has the time-dependence trait. + +This implementation declares that all vector fields support time-dependence queries. +Concrete `AbstractVectorField` instances have their time dependence encoded in the type parameter `TD`. + +See also: [`CTFlows.Traits.time_dependence`](@ref), [`CTFlows.Data.AbstractVectorField`](@ref). +""" +function Traits.has_time_dependence_trait(::AbstractVectorField) + return true +end + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractVectorField` has the variable-dependence trait. + +This implementation declares that all vector fields support variable-dependence queries. +Concrete `AbstractVectorField` instances have their variable dependence encoded in the type parameter `VD`. + +See also: [`CTFlows.Traits.variable_dependence`](@ref), [`CTFlows.Data.AbstractVectorField`](@ref). +""" +function Traits.has_variable_dependence_trait(::AbstractVectorField) + return true +end + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractVectorField` has the mutability trait. + +This implementation declares that all vector fields support mutability queries. +Concrete `AbstractVectorField` instances have their mutability encoded in the type parameter `MD`. + +See also: [`CTFlows.Traits.mutability_trait`](@ref), [`CTFlows.Data.AbstractVectorField`](@ref). +""" +function Traits.has_mutability_trait(::AbstractVectorField) + return true +end + +""" +$(TYPEDSIGNATURES) + +Extract the time dependence trait from an AbstractVectorField. + +# Returns +- `Type{<:TimeDependence}`: The time dependence trait type (Autonomous or NonAutonomous). + +# Example +\`\`\`julia +using CTFlows.Data +using CTFlows.Common + +vf = Data.VectorField(x -> -x; is_autonomous=true) +Traits.time_dependence(vf) # Returns Autonomous + +hvf = Data.HamiltonianVectorField((t, x, p) -> (x, -p); is_autonomous=false) +Traits.time_dependence(hvf) # Returns NonAutonomous +\`\`\` + +See also: [`CTFlows.Traits.has_time_dependence_trait`](@ref), [`CTFlows.Traits.is_autonomous`](@ref). +""" +function Traits.time_dependence(vf::AbstractVectorField{TD, <:Traits.VariableDependence, <:Traits.AbstractMutabilityTrait}) where {TD <: Traits.TimeDependence} + return TD +end + +""" +$(TYPEDSIGNATURES) + +Extract the variable dependence trait from an AbstractVectorField. + +# Returns +- `Type{<:VariableDependence}`: The variable dependence trait type (Fixed or NonFixed). + +# Example +\`\`\`julia +using CTFlows.Data +using CTFlows.Common + +vf = Data.VectorField(x -> -x; is_variable=false) +Traits.variable_dependence(vf) # Returns Fixed + +hvf = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_variable=true) +Traits.variable_dependence(hvf) # Returns NonFixed +\`\`\` + +See also: [`CTFlows.Traits.has_variable_dependence_trait`](@ref), [`CTFlows.Traits.is_variable`](@ref). +""" +function Traits.variable_dependence(vf::AbstractVectorField{<:Traits.TimeDependence, VD, <:Traits.AbstractMutabilityTrait}) where {VD <: Traits.VariableDependence} + return VD +end + +""" +$(TYPEDSIGNATURES) + +Extract the mutability trait from an AbstractVectorField. + +# Returns +- `Type{<:AbstractMutabilityTrait}`: The mutability trait type (InPlace or OutOfPlace). + +# Example +\`\`\`julia +using CTFlows.Data +using CTFlows.Common + +vf = Data.VectorField((dx, x) -> (dx .= -x; nothing)) +Traits.mutability_trait(vf) # Returns InPlace + +vf2 = Data.VectorField(x -> -x) +Traits.mutability_trait(vf2) # Returns OutOfPlace +\`\`\` + +See also: [`CTFlows.Traits.has_mutability_trait`](@ref), [`CTFlows.Traits.is_inplace`](@ref). +""" +function Traits.mutability_trait(vf::AbstractVectorField{<:Traits.TimeDependence, <:Traits.VariableDependence, MD}) where {MD <: Traits.AbstractMutabilityTrait} + return MD +end diff --git a/src/Data/hamiltonian.jl b/src/Data/hamiltonian.jl new file mode 100644 index 00000000..6656a983 --- /dev/null +++ b/src/Data/hamiltonian.jl @@ -0,0 +1,188 @@ +# ============================================================================= +# Concrete Hamiltonian type +# ============================================================================= + +""" +$(TYPEDEF) + +Parametric container for a scalar Hamiltonian function together with its +time-dependence and variable-dependence traits. + +The function returns a scalar value `H(t, x, p[, v]) โ†’ โ„` representing the +Hamiltonian of the system. + +# Type Parameters +- `F`: concrete type of the wrapped function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. + +# Fields +- `f::F`: the Hamiltonian function. + +# Construction + +Use the keyword constructor: + +```julia +Hamiltonian(f; is_autonomous = true, is_variable = false) # default: h(x, p) +Hamiltonian((t, x, p) -> ...; is_autonomous = false) # h(t, x, p) +Hamiltonian((x, p, v) -> ...; is_variable = true) # h(x, p, v) +Hamiltonian((t, x, p, v) -> ...; is_autonomous = false, is_variable = true) +``` + +# Call Signatures + +Every `Hamiltonian` is callable via its **natural** signature (matching the +traits), and via a **uniform** signature `(t, x, p, v)` that ignores the +unused arguments. + +For Autonomous/Fixed: natural `h(x, p)`, uniform `h(t, x, p, v)`. +For NonAutonomous/Fixed: natural `h(t, x, p)`, uniform `h(t, x, p, v)`. +For Autonomous/NonFixed: natural `h(x, p, v)`, uniform `h(t, x, p, v)`. +For NonAutonomous/NonFixed: natural `h(t, x, p, v)`, uniform `h(t, x, p, v)`. + +# Example +```julia-repl +julia> using CTFlows.Data, CTFlows.Common + +julia> h = Hamiltonian((x, p) -> dot(x, p)) # Uses defaults: is_autonomous=true, is_variable=false +Hamiltonian: autonomous, fixed (no variable) + natural call: h(x, p) + uniform call: h(t, x, p, v) + +julia> h = Hamiltonian((t, x, p) -> t * dot(x, p); is_autonomous=false) +Hamiltonian: non-autonomous, fixed (no variable) + natural call: h(t, x, p) + uniform call: h(t, x, p, v) +``` + +See also: [`CTFlows.Data.AbstractHamiltonian`](@ref), [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.VariableDependence`](@ref). +""" +struct Hamiltonian{F<:Function, TD, VD} <: AbstractHamiltonian{TD, VD} + f::F +end + +# ============================================================================= +# Constructor +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Construct a `Hamiltonian` with trait flags. + +# Arguments +- `f::Function`: The Hamiltonian function returning a scalar value. +- `is_autonomous::Bool`: If true, system is autonomous (default: `Common.__is_autonomous()`). +- `is_variable::Bool`: If true, system depends on variable parameters (default: `Common.__is_variable()`). + +# Returns +- `Hamiltonian`: A Hamiltonian with appropriate traits. + +# Example +```julia-repl +julia> using CTFlows.Data, CTFlows.Common + +julia> h = Hamiltonian((x, p) -> dot(x, p)) # Uses defaults: is_autonomous=true, is_variable=false +Hamiltonian: autonomous, fixed (no variable) + natural call: h(x, p) + uniform call: h(t, x, p, v) + +julia> h = Hamiltonian((t, x, p) -> t * dot(x, p); is_autonomous=false) +Hamiltonian: non-autonomous, fixed (no variable) + natural call: h(t, x, p) + uniform call: h(t, x, p, v) + +julia> h = Hamiltonian((x, p, v) -> v * dot(x, p); is_variable=true) +Hamiltonian: autonomous, non-fixed (variable) + natural call: h(x, p, v) + uniform call: h(t, x, p, v) +``` + +# Notes +- The default values for `is_autonomous` and `is_variable` come from `Common.__is_autonomous()` and `Common.__is_variable()`. +- The function signature should match the specified traits (e.g., if `is_autonomous=true` and `is_variable=false`, the function should accept `(x, p)`). + +See also: [`CTFlows.Data.Hamiltonian`](@ref), [`CTFlows.Traits.Autonomous`](@ref), [`CTFlows.Traits.NonAutonomous`](@ref), [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.NonFixed`](@ref). +""" +function Hamiltonian(f; + is_autonomous::Bool = Common.__is_autonomous(), + is_variable::Bool = Common.__is_variable() +) + TD = is_autonomous ? Traits.Autonomous : Traits.NonAutonomous + VD = is_variable ? Traits.NonFixed : Traits.Fixed + return Hamiltonian{typeof(f), TD, VD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.Fixed})(x, p) = H.f(x, p) +(H::Hamiltonian{<:Function, Traits.NonAutonomous, Traits.Fixed})(t, x, p) = H.f(t, x, p) +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.NonFixed})(x, p, v) = H.f(x, p, v) +(H::Hamiltonian{<:Function, Traits.NonAutonomous, Traits.NonFixed})(t, x, p, v) = H.f(t, x, p, v) + +# ============================================================================= +# Uniform (t, x, p, v) call - used by future HamiltonianSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.Fixed})(_, x, p, _) = H.f(x, p) +(H::Hamiltonian{<:Function, Traits.NonAutonomous, Traits.Fixed})(t, x, p, _) = H.f(t, x, p) +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.NonFixed})(_, x, p, v) = H.f(x, p, v) + +# ============================================================================= +# Base.show +# ============================================================================= + +""" + Base.show(io::IO, h::Hamiltonian{F, TD, VD}) + +Display a compact representation of a Hamiltonian showing its traits and call signatures. + +# Arguments +- `io::IO`: The IO stream. +- `h::Hamiltonian`: The Hamiltonian object. + +# Output +Displays three lines: +- Header with time and variable dependence traits +- Natural call signature +- Uniform call signature + +# Example +```julia-repl +julia> h = Hamiltonian((x, p) -> dot(x, p)) +Hamiltonian: autonomous, fixed (no variable) + natural call: h(x, p) + uniform call: h(t, x, p, v) +``` +""" +function Base.show(io::IO, ::Hamiltonian{F, TD, VD}) where {F, TD, VD} + header = "Hamiltonian: $(_td_label(TD)), $(_vd_label(VD))" + natural = _natural_sig_h(TD, VD) + uniform = _uniform_sig_h() + println(io, header) + println(io, " natural call: ", natural) + print(io, " uniform call: ", uniform) +end + +""" + Base.show(io::IO, mime::MIME"text/plain", h::Hamiltonian{F, TD, VD}) + +Display a Hamiltonian in the REPL with the same format as `Base.show(io, h)`. + +This method is called automatically when displaying a Hamiltonian in the Julia REPL. + +# Arguments +- `io::IO`: The IO stream. +- `mime::MIME"text/plain"`: The MIME type. +- `h::Hamiltonian`: The Hamiltonian object. + +See also: [`Base.show(io::IO, h::Hamiltonian)`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", h::Hamiltonian{F, TD, VD}) where {F, TD, VD} + show(io, h) +end diff --git a/src/Data/hamiltonian_vector_field.jl b/src/Data/hamiltonian_vector_field.jl new file mode 100644 index 00000000..6b9d1a46 --- /dev/null +++ b/src/Data/hamiltonian_vector_field.jl @@ -0,0 +1,267 @@ +""" +$(TYPEDEF) + +Parametric container for a Hamiltonian vector field function together with its +time-dependence, variable-dependence, and mutability traits. + +The function returns a tuple `(dx, dp)` representing the derivatives of state `x` +and costate `p` according to Hamiltonian dynamics. + +# Type Parameters +- `F`: concrete type of the wrapped function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. +- `MD <: AbstractMutabilityTrait`: `InPlace` or `OutOfPlace`. + +# Fields +- `f::F`: the Hamiltonian vector field function. + +# Construction + +Use the keyword constructor: + +```julia +HamiltonianVectorField(f; autonomous = true, variable = false) # default: f(x, p) +HamiltonianVectorField((t, x, p) -> ...; autonomous = false) # f(t, x, p) +HamiltonianVectorField((x, p, v) -> ...; variable = true) # f(x, p, v) +HamiltonianVectorField((t, x, p, v) -> ...; autonomous = false, variable = true) +``` + +The mutability trait (InPlace/OutOfPlace) is auto-detected from the function signature. + +# Call Signatures + +Every `HamiltonianVectorField` is callable via its **natural** signature (matching the +traits), and via a **uniform** signature `(t, x, p, v)` that ignores the +unused arguments. + +For InPlace Hamiltonian vector fields, the natural signature includes the derivative +buffers as the first two arguments (e.g., `(dx, dp, x, p)` for Autonomous/Fixed). + +See also: [`CTFlows.Data.AbstractVectorField`](@ref), [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.AbstractMutabilityTrait`](@ref). +""" +struct HamiltonianVectorField{F<:Function, TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} <: AbstractVectorField{TD, VD, MD} + f::F +end + +# ============================================================================= +# Internal helpers for mutability detection +# ============================================================================= + +""" + _oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for Autonomous/Fixed Hamiltonian vector fields (2: x, p). +""" +_oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = 2 + +""" + _oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for NonAutonomous/Fixed Hamiltonian vector fields (3: t, x, p). +""" +_oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) = 3 + +""" + _oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for Autonomous/NonFixed Hamiltonian vector fields (3: x, p, v). +""" +_oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) = 3 + +""" + _oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for NonAutonomous/NonFixed Hamiltonian vector fields (4: t, x, p, v). +""" +_oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) = 4 + +""" + _detect_mutability_hvf(f::Function, TD, VD) -> Type{<:AbstractMutabilityTrait} + +Detect the mutability trait from the Hamiltonian vector field function signature. + +Compares the function arity to the expected out-of-place arity and in-place arity +(arity + 2 for HamiltonianVectorField, which has two output buffers). Returns `InPlace` or `OutOfPlace` accordingly. + +If the function has multiple methods, throws a `PreconditionError` indicating that +auto-detection is ambiguous and the user should specify `is_inplace` explicitly. + +# Arguments +- `f::Function`: The Hamiltonian vector-field function. +- `TD`: Time dependence trait type. +- `VD`: Variable dependence trait type. + +# Returns +- `Type{InPlace}` or `Type{OutOfPlace}`. + +# Throws +- `Exceptions.PreconditionError`: If the function has multiple methods, making automatic arity detection ambiguous. +- `Exceptions.IncorrectArgument`: If the arity is invalid (does not match expected out-of-place or in-place arity). + +# Notes +- This function is called automatically by the `HamiltonianVectorField` constructor when `is_inplace` is `nothing`. +- Users can bypass auto-detection by specifying `is_inplace=true` or `is_inplace=false` explicitly in the constructor. +- HamiltonianVectorField has two output buffers (dx, dp), so the in-place arity is `oop_arity + 2`. + +See also: [`CTFlows.Data.HamiltonianVectorField`](@ref), [`CTFlows.Traits.InPlace`](@ref), [`CTFlows.Traits.OutOfPlace`](@ref). +""" +function _detect_mutability_hvf(f::Function, TD, VD) + method_count = length(methods(f)) + if method_count > 1 + throw(Exceptions.PreconditionError( + "Cannot auto-detect mutability: function has multiple methods"; + reason = "The function has $method_count methods, making automatic arity detection ambiguous", + suggestion = "Specify `is_inplace=true` or `is_inplace=false` explicitly in the constructor", + context = "HamiltonianVectorField mutability detection", + )) + end + + arity = first(methods(f)).nargs - 1 + oop_arity = _oop_arity_hvf(TD, VD) + ip_arity = oop_arity + 2 # HamiltonianVectorField has two output buffers (dx, dp) + + if arity == oop_arity + return Traits.OutOfPlace + elseif arity == ip_arity + return Traits.InPlace + else + throw(Exceptions.IncorrectArgument( + "Invalid function arity: expected $oop_arity (out-of-place) or $ip_arity (in-place), got $arity"; + suggestion = "Ensure your function signature matches the expected pattern for the given traits.", + context = "HamiltonianVectorField mutability detection", + )) + end +end + +""" +$(TYPEDSIGNATURES) + +Construct a `HamiltonianVectorField` with trait flags. + +# Arguments +- `f::Function`: The Hamiltonian vector field function returning `(dx, dp)`. +- `is_autonomous::Bool`: If true, system is autonomous (default: `Common.__is_autonomous()`). +- `is_variable::Bool`: If true, system depends on variable parameters (default: `Common.__is_variable()`). +- `is_inplace::Union{Bool, Nothing}`: If true, function is in-place; if false, function is out-of-place; if `nothing`, mutability is auto-detected from function signature (default: `Common.__is_inplace()`). + +# Returns +- `HamiltonianVectorField`: A HamiltonianVectorField with appropriate traits. + +# Example +```julia-repl +julia> using CTFlows.Systems, CTFlows.Common + +julia> hvf = HamiltonianVectorField((x, p) -> (x, -p)) # Uses defaults: is_autonomous=true, is_variable=false +HamiltonianVectorField: autonomous, fixed (no variable), out-of-place + natural : f(x, p) + uniform : f(t, x, p, v) + +julia> hvf = HamiltonianVectorField((t, x, p) -> (t .* x, -p); is_autonomous=false) +HamiltonianVectorField: non-autonomous, fixed (no variable), out-of-place + natural : f(t, x, p) + uniform : f(t, x, p, v) + +julia> hvf = HamiltonianVectorField((x, p) -> (x, -p); is_inplace=true) # Explicit in-place +HamiltonianVectorField: autonomous, fixed (no variable), in-place + natural : f(dx, dp, x, p) + uniform : f(dx, dp, t, x, p, v) +``` + +# Notes +- If `is_inplace` is `nothing` (default), the mutability is auto-detected from the function signature by checking the number of arguments. +- If the function has multiple methods, auto-detection will fail with a `PreconditionError`. In this case, specify `is_inplace` explicitly. + +See also: [`CTFlows.Data.HamiltonianVectorField`](@ref), [`CTFlows.Traits.Autonomous`](@ref), [`CTFlows.Traits.NonAutonomous`](@ref), [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.NonFixed`](@ref), [`CTFlows.Traits.InPlace`](@ref), [`CTFlows.Traits.OutOfPlace`](@ref). +""" +function HamiltonianVectorField(f; + is_autonomous::Bool = Common.__is_autonomous(), + is_variable::Bool = Common.__is_variable(), + is_inplace::Union{Bool, Nothing} = Common.__is_inplace() +) + TD = is_autonomous ? Traits.Autonomous : Traits.NonAutonomous + VD = is_variable ? Traits.NonFixed : Traits.Fixed + MD = if is_inplace === nothing + _detect_mutability_hvf(f, TD, VD) + else + is_inplace ? Traits.InPlace : Traits.OutOfPlace + end + return HamiltonianVectorField{typeof(f), TD, VD, MD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +# OutOfPlace signatures (existing) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(x, p) = H.f(x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, p) = H.f(t, x, p) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(x, p, v) = H.f(x, p, v) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, p, v) = H.f(t, x, p, v) + +# InPlace signatures (new) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, dp, x, p) = H.f(dx, dp, x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, dp, t, x, p) = H.f(dx, dp, t, x, p) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, dp, x, p, v) = H.f(dx, dp, x, p, v) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace})(dx, dp, t, x, p, v) = H.f(dx, dp, t, x, p, v) + +# ============================================================================= +# Uniform (t, x, p, v) call - used by HamiltonianVectorFieldSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +# OutOfPlace uniform signatures (existing) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(_, x, p, _) = H.f(x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, p, _) = H.f(t, x, p) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(_, x, p, v) = H.f(x, p, v) + +# InPlace uniform signatures (new) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, dp, _, x, p, _) = H.f(dx, dp, x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, dp, t, x, p, _) = H.f(dx, dp, t, x, p) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, dp, _, x, p, v) = H.f(dx, dp, x, p, v) + + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a HamiltonianVectorField. + +Shows the type name, time dependence, variable dependence, mutability, and function type. + +# Arguments +- `io::IO`: The IO stream to write to. +- `hvf::HamiltonianVectorField`: The HamiltonianVectorField to display. + +See also: [`CTFlows.Data.HamiltonianVectorField`](@ref). +""" +function Base.show(io::IO, ::HamiltonianVectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + header = "HamiltonianVectorField: $(_td_label(TD)), $(_vd_label(VD)), $(_md_label(MD))" + natural = _natural_sig_hvf(TD, VD, MD) + uniform = _uniform_sig_hvf(MD) + println(io, header) + println(io, " natural call: ", natural) + print(io, " uniform call: ", uniform) +end + +""" +$(TYPEDSIGNATURES) + +Display a HamiltonianVectorField in the REPL with text/plain MIME type. + +Delegates to the compact show method. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `hvf::HamiltonianVectorField`: The HamiltonianVectorField to display. + +See also: [`CTFlows.Data.HamiltonianVectorField`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", hvf::HamiltonianVectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + show(io, hvf) +end diff --git a/src/Data/helpers.jl b/src/Data/helpers.jl new file mode 100644 index 00000000..64a2a6ac --- /dev/null +++ b/src/Data/helpers.jl @@ -0,0 +1,258 @@ +""" +Internal helper functions for display formatting. + +This module provides helper functions for generating user-friendly display +representations of VectorField and HamiltonianVectorField types. +""" + +# ============================================================================= +# Shared label helpers +# ============================================================================= + +""" + _td_label(::Type{Traits.Autonomous}) -> String + _td_label(::Type{Traits.NonAutonomous}) -> String + +Return a user-friendly label for time dependence traits. + +# Arguments +- `TD`: Type parameter for time dependence (`Autonomous` or `NonAutonomous`) + +# Returns +- `String`: User-friendly label ("autonomous" or "non-autonomous") + +See also: [`_vd_label`](@ref), [`_md_label`](@ref). +""" +function _td_label(::Type{Traits.Autonomous}) + return "autonomous" +end +function _td_label(::Type{Traits.NonAutonomous}) + return "non-autonomous" +end + +""" + _vd_label(::Type{Traits.Fixed}) -> String + _vd_label(::Type{Traits.NonFixed}) -> String + +Return a user-friendly label for variable dependence traits. + +# Arguments +- `VD`: Type parameter for variable dependence (`Fixed` or `NonFixed`) + +# Returns +- `String`: User-friendly label ("fixed (no variable)" or "variable") + +See also: [`_td_label`](@ref), [`_md_label`](@ref). +""" +function _vd_label(::Type{Traits.Fixed}) + return "fixed (no variable)" +end +function _vd_label(::Type{Traits.NonFixed}) + return "variable" +end + +""" + _md_label(::Type{OutOfPlace}) -> String + _md_label(::Type{InPlace}) -> String + +Return a user-friendly label for mutability traits. + +# Arguments +- `MD`: Type parameter for mutability (`OutOfPlace` or `InPlace`) + +# Returns +- `String`: User-friendly label ("out-of-place" or "in-place") + +See also: [`_td_label`](@ref), [`_vd_label`](@ref). +""" +function _md_label(::Type{Traits.OutOfPlace}) + return "out-of-place" +end +function _md_label(::Type{Traits.InPlace}) + return "in-place" +end + +# ============================================================================= +# VectorField-specific signature helpers +# ============================================================================= + +""" + _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} -> String + _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} -> String + +Return the natural call signature for a VectorField based on its traits. + +# Arguments +- `TD`: Time dependence type (`Autonomous` or `NonAutonomous`) +- `VD`: Variable dependence type (`Fixed` or `NonFixed`) +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Natural call signature (e.g., "f(x)", "f(t, x)", "f(dx, x)") + +# Example +\`\`\`julia +_natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(x)" +_natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(t, x)" +_natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) # Returns "f(dx, x)" +\`\`\` + +See also: [`_uniform_sig_vf`](@ref). +""" +function _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +function _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} + args = ["dx"] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_vf(::Type{Traits.OutOfPlace}) -> String + _uniform_sig_vf(::Type{Traits.InPlace}) -> String + +Return the uniform call signature for a VectorField. + +The uniform signature always includes all arguments (t, x, v) regardless of traits, +and includes the derivative buffer (dx) for in-place variants. + +# Arguments +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Uniform call signature ("f(t, x, v)" or "f(dx, t, x, v)") + +See also: [`_natural_sig_vf`](@ref). +""" +function _uniform_sig_vf(::Type{Traits.OutOfPlace}) + return "f(t, x, v)" +end +function _uniform_sig_vf(::Type{Traits.InPlace}) + return "f(dx, t, x, v)" +end + +# ============================================================================= +# HamiltonianVectorField-specific signature helpers +# ============================================================================= + +""" + _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} -> String + _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} -> String + +Return the natural call signature for a HamiltonianVectorField based on its traits. + +# Arguments +- `TD`: Time dependence type (`Autonomous` or `NonAutonomous`) +- `VD`: Variable dependence type (`Fixed` or `NonFixed`) +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Natural call signature (e.g., "f(x, p)", "f(t, x, p)", "f(dx, dp, x, p)") + +# Example +\`\`\`julia +_natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(x, p)" +_natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(t, x, p)" +_natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) # Returns "f(dx, dp, x, p)" +\`\`\` + +See also: [`_uniform_sig_hvf`](@ref). +""" +function _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +function _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} + args = ["dx", "dp"] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_hvf(::Type{Traits.OutOfPlace}) -> String + _uniform_sig_hvf(::Type{Traits.InPlace}) -> String + +Return the uniform call signature for a HamiltonianVectorField. + +The uniform signature always includes all arguments (t, x, p, v) regardless of traits, +and includes the derivative buffers (dx, dp) for in-place variants. + +# Arguments +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Uniform call signature ("f(t, x, p, v)" or "f(dx, dp, t, x, p, v)") + +See also: [`_natural_sig_hvf`](@ref). +""" +function _uniform_sig_hvf(::Type{Traits.OutOfPlace}) + return "f(t, x, p, v)" +end +function _uniform_sig_hvf(::Type{Traits.InPlace}) + return "f(dx, dp, t, x, p, v)" +end + +# ============================================================================= +# Hamiltonian-specific signature helpers +# ============================================================================= + +""" + _natural_sig_h(::Type{TD}, ::Type{VD}) where {TD, VD} -> String + +Return the natural call signature for a Hamiltonian based on its traits. + +# Arguments +- `TD`: Time dependence type (`Autonomous` or `NonAutonomous`) +- `VD`: Variable dependence type (`Fixed` or `NonFixed`) + +# Returns +- `String`: Natural call signature (e.g., "h(x, p)", "h(t, x, p)") + +# Example +\`\`\`julia +_natural_sig_h(Autonomous, Fixed) # Returns "h(x, p)" +_natural_sig_h(NonAutonomous, Fixed) # Returns "h(t, x, p)" +\`\`\` + +See also: [`_uniform_sig_h`](@ref). +""" +function _natural_sig_h(::Type{TD}, ::Type{VD}) where {TD, VD} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + VD === Traits.NonFixed && push!(args, "v") + return "h(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_h() -> String + +Return the uniform call signature for a Hamiltonian. + +The uniform signature always includes all arguments (t, x, p, v) regardless of traits. + +# Returns +- `String`: Uniform call signature ("h(t, x, p, v)") + +See also: [`_natural_sig_h`](@ref). +""" +function _uniform_sig_h() + return "h(t, x, p, v)" +end diff --git a/src/Data/vector_field.jl b/src/Data/vector_field.jl new file mode 100644 index 00000000..7f7b21a1 --- /dev/null +++ b/src/Data/vector_field.jl @@ -0,0 +1,263 @@ +""" +$(TYPEDEF) + +Parametric container for a vector-field function together with its +time-dependence, variable-dependence, and mutability traits. + +# Type Parameters +- `F`: concrete type of the wrapped function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. +- `MD <: AbstractMutabilityTrait`: `InPlace` or `OutOfPlace`. + +# Fields +- `f::F`: the vector-field function. + +# Construction + +Use the keyword constructor: + +```julia +VectorField(f; autonomous = true, variable = false) # default: f(x) +VectorField((t, x) -> ...; autonomous = false) # f(t, x) +VectorField((x, v) -> ...; variable = true) # f(x, v) +VectorField((t, x, v) -> ...; autonomous = false, variable = true) +``` + +The mutability trait (InPlace/OutOfPlace) is auto-detected from the function signature. + +# Call Signatures + +Every `VectorField` is callable via its **natural** signature (matching the +traits), and via a **uniform** signature `(t, x, v)` that ignores the +unused arguments โ€” this uniform form is used internally to build the right-hand +side of the ODE in a trait-agnostic way. + +For InPlace vector fields, the natural signature includes the derivative buffer +as the first argument (e.g., `(dx, x)` for Autonomous/Fixed). + +See also: [`CTFlows.Data.AbstractVectorField`](@ref), [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.AbstractMutabilityTrait`](@ref). +""" +struct VectorField{F<:Function, TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} <: AbstractVectorField{TD, VD, MD} + f::F +end + +# ============================================================================= +# Internal helpers for mutability detection +# ============================================================================= + +""" + _oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for Autonomous/Fixed vector fields (1: x). +""" +_oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = 1 + +""" + _oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for NonAutonomous/Fixed vector fields (2: t, x). +""" +_oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) = 2 + +""" + _oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for Autonomous/NonFixed vector fields (2: x, v). +""" +_oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) = 2 + +""" + _oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for NonAutonomous/NonFixed vector fields (3: t, x, v). +""" +_oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) = 3 + +""" + _detect_mutability_vf(f::Function, TD, VD) -> Type{<:AbstractMutabilityTrait} + +Detect the mutability trait from the function signature. + +Compares the function arity to the expected out-of-place arity and in-place arity +(arity + 1 for VectorField). Returns `InPlace` or `OutOfPlace` accordingly. + +If the function has multiple methods, throws a `PreconditionError` indicating that +auto-detection is ambiguous and the user should specify `is_inplace` explicitly. + +# Arguments +- `f::Function`: The vector-field function. +- `TD`: Time dependence trait type. +- `VD`: Variable dependence trait type. + +# Returns +- `Type{InPlace}` or `Type{OutOfPlace}`. + +# Throws +- `Exceptions.PreconditionError`: If the function has multiple methods, making automatic arity detection ambiguous. +- `Exceptions.IncorrectArgument`: If the arity is invalid (does not match expected out-of-place or in-place arity). + +# Notes +- This function is called automatically by the `VectorField` constructor when `is_inplace` is `nothing`. +- Users can bypass auto-detection by specifying `is_inplace=true` or `is_inplace=false` explicitly in the constructor. + +See also: [`CTFlows.Data.VectorField`](@ref), [`CTFlows.Traits.InPlace`](@ref), [`CTFlows.Traits.OutOfPlace`](@ref). +""" +function _detect_mutability_vf(f::Function, TD, VD) + method_count = length(methods(f)) + if method_count > 1 + throw(Exceptions.PreconditionError( + "Cannot auto-detect mutability: function has multiple methods"; + reason = "The function has $method_count methods, making automatic arity detection ambiguous", + suggestion = "Specify `is_inplace=true` or `is_inplace=false` explicitly in the constructor", + context = "VectorField mutability detection", + )) + end + + arity = first(methods(f)).nargs - 1 + oop_arity = _oop_arity_vf(TD, VD) + ip_arity = oop_arity + 1 + + if arity == oop_arity + return Traits.OutOfPlace + elseif arity == ip_arity + return Traits.InPlace + else + throw(Exceptions.IncorrectArgument( + "Invalid function arity: expected $oop_arity (out-of-place) or $ip_arity (in-place), got $arity"; + suggestion = "Ensure your function signature matches the expected pattern for the given traits.", + context = "VectorField mutability detection", + )) + end +end + +""" +$(TYPEDSIGNATURES) + +Construct a `VectorField` with trait flags. + +# Arguments +- `f::Function`: The vector-field function. +- `is_autonomous::Bool`: If true, system is autonomous (default: `Common.__is_autonomous()`). +- `is_variable::Bool`: If true, system depends on variable parameters (default: `Common.__is_variable()`). +- `is_inplace::Union{Bool, Nothing}`: If true, function is in-place; if false, function is out-of-place; if `nothing`, mutability is auto-detected from function signature (default: `Common.__is_inplace()`). + +# Returns +- `VectorField`: A VectorField with appropriate traits. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Systems, CTFlows.Common + +julia> vf = VectorField(x -> -x) # Uses defaults: is_autonomous=true, is_variable=false +VectorField: autonomous, fixed (no variable), out-of-place + natural : f(x) + uniform : f(t, x, v) + +julia> vf = VectorField((t, x) -> t .* x; is_autonomous=false) +VectorField: non-autonomous, fixed (no variable), out-of-place + natural : f(t, x) + uniform : f(t, x, v) + +julia> vf = VectorField(x -> -x; is_inplace=true) # Explicit in-place +VectorField: autonomous, fixed (no variable), in-place + natural : f(dx, x) + uniform : f(dx, t, x, v) +\`\`\` + +# Notes +- If `is_inplace` is `nothing` (default), the mutability is auto-detected from the function signature by checking the number of arguments. +- If the function has multiple methods, auto-detection will fail with a `PreconditionError`. In this case, specify `is_inplace` explicitly. + +See also: [`CTFlows.Data.VectorField`](@ref), [`CTFlows.Traits.Autonomous`](@ref), [`CTFlows.Traits.NonAutonomous`](@ref), [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.NonFixed`](@ref), [`CTFlows.Traits.InPlace`](@ref), [`CTFlows.Traits.OutOfPlace`](@ref). +""" +function VectorField(f; + is_autonomous::Bool = Common.__is_autonomous(), + is_variable::Bool = Common.__is_variable(), + is_inplace::Union{Bool, Nothing} = Common.__is_inplace() +) + TD = is_autonomous ? Traits.Autonomous : Traits.NonAutonomous + VD = is_variable ? Traits.NonFixed : Traits.Fixed + MD = if is_inplace === nothing + _detect_mutability_vf(f, TD, VD) + else + is_inplace ? Traits.InPlace : Traits.OutOfPlace + end + return VectorField{typeof(f), TD, VD, MD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +# OutOfPlace signatures (existing) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(x) = F.f(x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x) = F.f(t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(x, v) = F.f(x, v) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, v) = F.f(t, x, v) + +# InPlace signatures (new) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, x) = F.f(dx, x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, t, x) = F.f(dx, t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, x, v) = F.f(dx, x, v) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, t, x, v) + +# ============================================================================= +# Uniform (t, x, v) call - used by VectorFieldSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +# OutOfPlace uniform signatures (existing) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, v) = F.f(x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, v) = F.f(t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, v) = F.f(x, v) + +# InPlace uniform signatures (new) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, x, v) + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a VectorField. + +Shows the type name, time dependence, variable dependence, mutability, and function type. + +# Arguments +- `io::IO`: The IO stream to write to. +- `vf::VectorField`: The VectorField to display. + +See also: [`CTFlows.Data.VectorField`](@ref). +""" +function Base.show(io::IO, vf::VectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + header = "VectorField: $(_td_label(TD)), $(_vd_label(VD)), $(_md_label(MD))" + natural = _natural_sig_vf(TD, VD, MD) + uniform = _uniform_sig_vf(MD) + println(io, header) + println(io, " natural call: ", natural) + print(io, " uniform call: ", uniform) +end + +""" +$(TYPEDSIGNATURES) + +Display a VectorField in the REPL with text/plain MIME type. + +Delegates to the compact show method. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `vf::VectorField`: The VectorField to display. + +See also: [`CTFlows.Data.VectorField`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", vf::VectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + show(io, vf) +end diff --git a/src/Differentiation/Differentiation.jl b/src/Differentiation/Differentiation.jl new file mode 100644 index 00000000..81dc05e5 --- /dev/null +++ b/src/Differentiation/Differentiation.jl @@ -0,0 +1,77 @@ +# ============================================================================== +# Differentiation โ€” AD Backend Strategies for Hamiltonian Gradients +# ============================================================================== + +""" +Module `CTFlows.Differentiation` provides automatic differentiation backend strategies +for computing gradients of scalar Hamiltonian functions. + +## Architecture + +The module defines an abstract contract `AbstractADBackend` with three methods: +- `hamiltonian_gradient(backend, h, t, x, p, v, cache)` โ†’ (โˆ‚H/โˆ‚x, โˆ‚H/โˆ‚p) +- `variable_gradient(backend, h, t, x, p, v, cache)` โ†’ โˆ‚H/โˆ‚v +- `prepare_cache(backend, h, typical_t, typical_x, typical_p, typical_v)` โ†’ AbstractCache + +The concrete strategy `DifferentiationInterface` wraps DifferentiationInterface.jl backends +(e.g., `AutoForwardDiff()`) and stores them in its `:ad_backend` option. + +## Dependencies + +- `ADTypes.jl` (hard dependency) โ€” provides `AutoForwardDiff` type +- `CTSolvers.Strategies` (from CTSolvers) โ€” strategy contract +- `CTBase.Exceptions` โ€” `NotImplemented` for stub methods +- `Common` (sibling) โ€” `AbstractCache` type + +## Extension + +Gradient computation requires the `CTFlowsDifferentiationInterface` extension (Phase 5), +which implements the three contract methods using `DifferentiationInterface.gradient` +and `DifferentiationInterface.prepare_gradient`. + +## Exports + +- `AbstractADBackend` +- `DifferentiationInterface` +- `build_ad_backend` +- `hamiltonian_gradient` +- `variable_gradient` +- `prepare_cache` +""" +module Differentiation + +# ============================================================================== +# External Imports +# ============================================================================== + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using CTSolvers: CTSolvers +using ADTypes: ADTypes # Hard dep โ€” provides AutoForwardDiff + +# ============================================================================== +# Internal Sibling Imports +# ============================================================================== + +import ..Common: Common + +# ============================================================================== +# Includes (in dependency order) +# ============================================================================== + +include("abstract_ad_backend.jl") +include("differentiation_interface.jl") +include("building.jl") + +# ============================================================================== +# Exports +# ============================================================================== + +export AbstractADBackend +export DifferentiationInterface +export build_ad_backend +export hamiltonian_gradient +export variable_gradient +export prepare_cache + +end # module diff --git a/src/Differentiation/abstract_ad_backend.jl b/src/Differentiation/abstract_ad_backend.jl new file mode 100644 index 00000000..097789e2 --- /dev/null +++ b/src/Differentiation/abstract_ad_backend.jl @@ -0,0 +1,150 @@ +# ============================================================================== +# AbstractADBackend โ€” AD Backend Strategy Contract +# ============================================================================== + +""" +$(TYPEDEF) + +Abstract base type for automatic differentiation backends in CTFlows. + +An `AbstractADBackend` is a strategy that defines how to compute gradients of a +scalar Hamiltonian function. Concrete backends (e.g., `DifferentiationInterface`) +implement the contract methods to provide actual gradient computation. + +# Notes + - `AbstractADBackend` subtypes `CTSolvers.Strategies.AbstractStrategy` โ€” they are + first-class strategies in the CTSolvers ecosystem. + - The contract consists of three methods: `hamiltonian_gradient`, `variable_gradient`, + and `prepare_cache`. Concrete backends must implement all three. + - Gradient methods return **non-negated** partial derivatives; the RHS closures + apply the signs (แน— = -โˆ‚H/โˆ‚x, แนฝ = -โˆ‚H/โˆ‚v). + +See also: [`CTFlows.Differentiation.DifferentiationInterface`](@ref), +[`CTFlows.Differentiation.hamiltonian_gradient`](@ref), +[`CTFlows.Differentiation.variable_gradient`](@ref), +[`CTFlows.Differentiation.prepare_cache`](@ref). +""" +abstract type AbstractADBackend <: CTSolvers.Strategies.AbstractStrategy end + +# ============================================================================== +# Contract Methods +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Compute the Hamiltonian gradient (โˆ‚H/โˆ‚x, โˆ‚H/โˆ‚p) using the backend. + +# Arguments +- `backend::AbstractADBackend`: The AD backend. +- `h`: The Hamiltonian function or type. +- `t`: Time (scalar). +- `x`: State vector. +- `p`: Costate vector. +- `v`: Variable (scalar or `nothing` for Fixed problems). +- `cache`: Optional pre-allocated cache for efficient computation. + +# Returns +- `(โˆ‚H_โˆ‚x, โˆ‚H_โˆ‚p)`: Tuple of partial derivatives, **non-negated**. The RHS closure + is responsible for applying the signs (แน— = -โˆ‚H/โˆ‚x). + +# Throws +- `CTBase.Exceptions.NotImplemented`: If the concrete backend does not implement + this method. + +# Notes + - The cache defaults to `nothing` so calls without a prepared cache still work. + - Concrete backends should fall back to plain gradient computation when cache is + `nothing`, and use the prepared cache when available for efficiency. + +See also: [`CTFlows.Differentiation.variable_gradient`](@ref), +[`CTFlows.Differentiation.prepare_cache`](@ref). +""" +function hamiltonian_gradient(backend::AbstractADBackend, h, t, x, p, v, cache) + throw(Exceptions.NotImplemented( + "hamiltonian_gradient not implemented for $(typeof(backend))", + required_method = "hamiltonian_gradient(backend::$(typeof(backend)), h, t, x, p, v[, cache])", + suggestion = "Implement hamiltonian_gradient for $(typeof(backend)) or load an extension that provides gradient computation (e.g., CTFlowsDifferentiationInterface)", + context = "AD backend contract" + )) +end + +""" +$(TYPEDSIGNATURES) + +Compute the variable gradient โˆ‚H/โˆ‚v using the backend. + +# Arguments +- `backend::AbstractADBackend`: The AD backend. +- `h`: The Hamiltonian function or type. +- `t`: Time (scalar). +- `x`: State vector. +- `p`: Costate vector. +- `v`: Variable (scalar or `nothing` for Fixed problems). +- `cache`: Optional pre-allocated cache for efficient computation. + +# Returns +- `โˆ‚H_โˆ‚v`: Partial derivative with respect to the variable, **non-negated**. The RHS + closure is responsible for applying the sign (แนฝ = -โˆ‚H/โˆ‚v). + +# Throws +- `CTBase.Exceptions.NotImplemented`: If the concrete backend does not implement + this method. + +# Notes + - For Fixed problems (`v === nothing`), this method should return `nothing` or + throw an error depending on the backend's contract. + - The cache defaults to `nothing` so calls without a prepared cache still work. + +See also: [`CTFlows.Differentiation.hamiltonian_gradient`](@ref), +[`CTFlows.Differentiation.prepare_cache`](@ref). +""" +function variable_gradient(backend::AbstractADBackend, h, t, x, p, v, cache) + throw(Exceptions.NotImplemented( + "variable_gradient not implemented for $(typeof(backend))", + required_method = "variable_gradient(backend::$(typeof(backend)), h, t, x, p, v[, cache])", + suggestion = "Implement variable_gradient for $(typeof(backend)) or load an extension that provides gradient computation (e.g., CTFlowsDifferentiationInterface)", + context = "AD backend contract" + )) +end + +""" +$(TYPEDSIGNATURES) + +Prepare a cache for efficient gradient computation using typical values. + +# Arguments +- `backend::AbstractADBackend`: The AD backend. +- `h`: The Hamiltonian function or type. +- `typical_t`: Typical time value (used for cache pre-allocation). +- `typical_x`: Typical state vector (used for cache pre-allocation). +- `typical_p`: Typical costate vector (used for cache pre-allocation). +- `typical_v`: Typical variable value (scalar or `nothing` for Fixed problems). + +# Returns +- `Common.AbstractCache`: A concrete cache subtype containing pre-allocated buffers + and prepared differentiation plans. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If the concrete backend does not implement + this method. + +# Notes + - The cache is passed to gradient methods via the `cache` argument. + - Concrete cache types are extension-specific (e.g., `DifferentiationInterfaceCache` + from the `CTFlowsDifferentiationInterface` extension). + - The cache is stored in `ODEParameters` and accessed during ODE integration. + +See also: [`CTFlows.Differentiation.hamiltonian_gradient`](@ref), +[`CTFlows.Differentiation.variable_gradient`](@ref), +[`CTFlows.Common.AbstractCache`](@ref), +[`CTFlows.Common.ODEParameters`](@ref). +""" +function prepare_cache(backend::AbstractADBackend, h, typical_t, typical_x, typical_p, typical_v) + throw(Exceptions.NotImplemented( + "prepare_cache not implemented for $(typeof(backend))", + required_method = "prepare_cache(backend::$(typeof(backend)), h, typical_t, typical_x, typical_p, typical_v)", + suggestion = "Implement prepare_cache for $(typeof(backend)) or load an extension that provides cache preparation (e.g., CTFlowsDifferentiationInterface)", + context = "AD backend contract" + )) +end diff --git a/src/Differentiation/building.jl b/src/Differentiation/building.jl new file mode 100644 index 00000000..0fdc1826 --- /dev/null +++ b/src/Differentiation/building.jl @@ -0,0 +1,25 @@ +# ============================================================================== +# build_ad_backend โ€” Factory for AD Backends +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Factory function to build an AD backend with default options. + +# Arguments +- `kwargs...`: Options passed to `DifferentiationInterface` constructor. + +# Returns +- `DifferentiationInterface`: A new AD backend strategy instance. + +# Notes + - This is a convenience factory that always returns a `DifferentiationInterface` + instance with the provided options. + - Parallel to `Integrators.build_integrator` for SciML integrators. + +See also: [`CTFlows.Differentiation.DifferentiationInterface`](@ref). +""" +function build_ad_backend(; kwargs...) + return DifferentiationInterface(; kwargs...) +end diff --git a/src/Differentiation/differentiation_interface.jl b/src/Differentiation/differentiation_interface.jl new file mode 100644 index 00000000..5c4f2a01 --- /dev/null +++ b/src/Differentiation/differentiation_interface.jl @@ -0,0 +1,91 @@ +# ============================================================================== +# DifferentiationInterface โ€” Concrete AD Backend Strategy +# ============================================================================== + +""" +$(TYPEDEF) + +Concrete AD backend strategy wrapping DifferentiationInterface.jl backends. + +`DifferentiationInterface` stores a `:ad_backend` option (e.g., `AutoForwardDiff()`, +`AutoZygote()`) and uses it to compute gradients via the DifferentiationInterface.jl +ecosystem. + +# Arguments +- `backend=AutoForwardDiff()`: The DifferentiationInterface.jl backend to use. Defaults + to `AutoForwardDiff()` from `ADTypes.jl` (a hard dependency). +- `kwargs...`: Additional options passed to `StrategyOptions`. + +# Notes + - `ADTypes.jl` is a hard dependency, so `AutoForwardDiff()` is always available in core. + - Gradient computation requires the `CTFlowsDifferentiationInterface` extension (Phase 5). + - Without the extension, the gradient methods throw `NotImplemented` with a helpful message. + +See also: [`CTFlows.Differentiation.AbstractADBackend`](@ref), +[`CTFlows.Differentiation.hamiltonian_gradient`](@ref), +[`CTFlows.Differentiation.variable_gradient`](@ref), +[`CTFlows.Differentiation.prepare_cache`](@ref). +""" +struct DifferentiationInterface{O<:CTSolvers.Strategies.StrategyOptions} <: + AbstractADBackend + options::O +end + +""" +$(TYPEDSIGNATURES) + +Constructor for `DifferentiationInterface` with a specific backend. + +# Arguments +- `backend=AutoForwardDiff()`: The DifferentiationInterface.jl backend. +- `kwargs...`: Additional options passed to `StrategyOptions`. + +# Returns +- `DifferentiationInterface`: A new backend strategy instance. +""" +function DifferentiationInterface(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(DifferentiationInterface; mode=mode, kwargs...) + return DifferentiationInterface{typeof(opts)}(opts) +end + +# ============================================================================== +# CTSolvers.Strategies Contract +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Return the strategy identifier for `DifferentiationInterface`. +""" +CTSolvers.Strategies.id(::Type{<:DifferentiationInterface}) = :di + +""" +$(TYPEDSIGNATURES) + +Return a human-readable description of the `DifferentiationInterface` strategy. +""" +CTSolvers.Strategies.description(::Type{<:DifferentiationInterface}) = + "AD backend wrapping DifferentiationInterface.jl backends (e.g., AutoForwardDiff)." + +""" +$(TYPEDSIGNATURES) + +Return metadata defining `DifferentiationInterface` options and their specifications. +""" +function CTSolvers.Strategies.metadata(::Type{<:DifferentiationInterface}) + return CTSolvers.Strategies.StrategyMetadata( + CTSolvers.Strategies.OptionDefinition(; + name = :ad_backend, + type = ADTypes.AbstractADType, + default = ADTypes.AutoForwardDiff(), # ADTypes.jl โ€” always available (hard dep) + description = "DifferentiationInterface.jl backend (e.g. AutoForwardDiff()).", + aliases=(:backend, :ad), + ), + CTSolvers.Strategies.OptionDefinition(; + name = :prepare_cache, + type = Bool, + default = false, + description = "If true, call DI.prepare_gradient once at cache-preparation time and reuse the plan. If false, call plain DI.gradient at every integration step (no preallocation).", + ), + ) +end diff --git a/src/Flows/Flows.jl b/src/Flows/Flows.jl new file mode 100644 index 00000000..1bd79a4b --- /dev/null +++ b/src/Flows/Flows.jl @@ -0,0 +1,53 @@ +""" + Flows + +Flow types and contracts for CTFlows. + +This module defines the `AbstractFlow` type and its required methods: +- `(flow)(t0, x0, tf)`: callable interface for state integration +- `(flow)(t0, x0, p0, tf)`: callable interface for state + costate integration +- `system`: returns the system associated with the flow +- `integrator`: returns the integrator used by the flow +""" +module Flows + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using CTSolvers: CTSolvers + +# ============================================================================== +# Internal sibling-submodule imports +# ============================================================================== + +import ..Common: Common +import ..Configs: Configs +import ..Traits: Traits +import ..Data: Data +import ..Differentiation: Differentiation +import ..Systems: Systems +import ..Integrators: Integrators +import ..Solutions: Solutions + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "abstract_flow.jl")) +include(joinpath(@__DIR__, "flow.jl")) +include(joinpath(@__DIR__, "registry.jl")) +include(joinpath(@__DIR__, "flow_routing.jl")) +include(joinpath(@__DIR__, "building.jl")) +include(joinpath(@__DIR__, "calling.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractFlow, AbstractStateFlow, AbstractHamiltonianFlow, Flow, StateFlow, HamiltonianFlow +export system, integrator +export call +export build_flow +export prepare_cache + +end # module Flows diff --git a/src/Flows/abstract_flow.jl b/src/Flows/abstract_flow.jl new file mode 100644 index 00000000..37f3502a --- /dev/null +++ b/src/Flows/abstract_flow.jl @@ -0,0 +1,383 @@ +""" +$(TYPEDEF) + +Abstract type for all flows in CTFlows. + +An `AbstractFlow` is a callable object that combines an `AbstractSystem` with an +`AbstractIntegrator`. It carries no business logic of its own โ€” its job is +to expose the integration protocol. + +# Interface Requirements + +All subtypes must implement: +- `system(flow::AbstractFlow)`: Return the associated `AbstractSystem`. +- `integrator(flow::AbstractFlow)`: Return the associated `AbstractIntegrator`. + +# Traits + +All `AbstractFlow` subtypes automatically support time-dependence and variable-dependence +trait queries encoded in their type parameters: +- `time_dependence(flow)`: Returns the time-dependence trait type. +- `variable_dependence(flow)`: Returns the variable-dependence trait type. +- `is_autonomous(flow)`, `is_nonautonomous(flow)`: Time-dependence predicates. +- `is_variable(flow)`, `is_nonvariable(flow)`, `has_variable(flow)`: Variable-dependence predicates. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> MyFlow <: Flows.AbstractFlow +true +\`\`\` + +See also: [`CTFlows.Flows.Flow`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref), [`CTFlows.Integrators.AbstractIntegrator`](@ref). +""" +abstract type AbstractFlow{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence} end + +""" +$(TYPEDEF) + +Abstract type for state flows. + +Subtype of `AbstractFlow` specialized for state systems (not Hamiltonian systems). +Carries the system type parameter `S` which must be an `AbstractStateSystem`. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `S <: AbstractStateSystem{TD, VD}`: The state system type + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> MyStateFlow <: Flows.AbstractStateFlow +true +\`\`\` + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Flows.AbstractHamiltonianFlow`](@ref), [`CTFlows.Systems.AbstractStateSystem`](@ref). +""" +abstract type AbstractStateFlow{TD, VD, S<:Systems.AbstractStateSystem{TD,VD}} <: AbstractFlow{TD, VD} end + +""" +$(TYPEDEF) + +Abstract type for Hamiltonian flows. + +Subtype of `AbstractFlow` specialized for Hamiltonian systems. +Carries the system type parameter `S` which must be an `AbstractHamiltonianSystem`. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `S <: AbstractHamiltonianSystem{TD, VD}`: The Hamiltonian system type + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> MyHamiltonianFlow <: Flows.AbstractHamiltonianFlow +true +\`\`\` + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Flows.AbstractStateFlow`](@ref), [`CTFlows.Systems.AbstractHamiltonianSystem`](@ref). +""" +abstract type AbstractHamiltonianFlow{TD, VD, S<:Systems.AbstractHamiltonianSystem{TD,VD,<:Traits.AbstractADTrait}} <: AbstractFlow{TD, VD} end + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractFlow` has the time-dependence trait. + +# Returns +- `Bool`: Always `true` for `AbstractFlow`. + +See also: [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.time_dependence`](@ref). +""" +Traits.has_time_dependence_trait(::AbstractFlow) = true + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractFlow` has the variable-dependence trait. + +# Returns +- `Bool`: Always `true` for `AbstractFlow`. + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.variable_dependence`](@ref). +""" +Traits.has_variable_dependence_trait(::AbstractFlow) = true + +""" +$(TYPEDSIGNATURES) + +Extract the time dependence trait from an `AbstractFlow`. + +# Returns +- `Type{<:TimeDependence}`: The time dependence trait type (Autonomous or NonAutonomous). + +# Example +\`\`\`julia +using CTFlows.Flows +using CTFlows.Common + +struct MyFlow <: Flows.AbstractFlow{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +Traits.time_dependence(MyFlow) # Returns Autonomous +\`\`\` + +See also: [`CTFlows.Traits.has_time_dependence_trait`](@ref), [`CTFlows.Traits.is_autonomous`](@ref), [`CTFlows.Flows.AbstractFlow`](@ref). +""" +function Traits.time_dependence(flow::AbstractFlow{TD, <:Traits.VariableDependence}) where {TD <: Traits.TimeDependence} + return TD +end + +""" +$(TYPEDSIGNATURES) + +Extract the variable dependence trait from an `AbstractFlow`. + +# Returns +- `Type{<:VariableDependence}`: The variable dependence trait type (Fixed or NonFixed). + +# Example +\`\`\`julia +using CTFlows.Flows +using CTFlows.Common + +struct MyFlow <: Flows.AbstractFlow{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +Traits.variable_dependence(MyFlow) # Returns Fixed +\`\`\` + +See also: [`CTFlows.Traits.has_variable_dependence_trait`](@ref), [`CTFlows.Traits.is_variable`](@ref), [`CTFlows.Flows.AbstractFlow`](@ref). +""" +function Traits.variable_dependence(flow::AbstractFlow{<:Traits.TimeDependence, VD}) where {VD <: Traits.VariableDependence} + return VD +end + +""" +$(TYPEDSIGNATURES) + +Return the automatic differentiation capability trait of a flow. + +# Returns +- `Type{<:AbstractADTrait}`: The AD capability trait, either `WithAD` or `WithoutAD`. + +# Notes +- Default implementation returns `WithoutAD` for all flows +- Specialized implementation on `AbstractHamiltonianFlow` delegates to the system's trait +- This trait is used for dispatch in cache preparation and augmented integration + +See also: [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Traits.WithAD`](@ref), [`CTFlows.Traits.WithoutAD`](@ref). +""" +Traits.ad_trait(::AbstractFlow) = Traits.WithoutAD + +""" +$(TYPEDSIGNATURES) + +Return the automatic differentiation capability trait of a Hamiltonian flow. + +# Returns +- `Type{<:AbstractADTrait}`: The AD capability trait from the flow's system. + +# Notes +- Delegates to the system's AD trait via `Common.ad_trait(system(flow))` +- This enables dispatch based on whether the flow was built from a scalar Hamiltonian or a vector field + +See also: [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Traits.ad_trait`](@ref), [`CTFlows.Systems.AbstractHamiltonianSystem`](@ref). +""" +Traits.ad_trait(f::AbstractHamiltonianFlow) = Traits.ad_trait(system(f)) + +""" +$(TYPEDSIGNATURES) + +Return the variable costate capability trait of a flow. + +# Returns +- `Type{<:AbstractVariableCostateCapability}`: The capability trait, either + `SupportsVariableCostate` or `NoVariableCostate`. + +# Notes +- Default implementation returns `NoVariableCostate` for all flows +- Specialized implementation on `AbstractHamiltonianFlow` delegates to the system's trait +- This trait is used for dispatch in `call_variable_costate` to determine if augmented integration is possible + +See also: [`CTFlows.Traits.AbstractVariableCostateCapability`](@ref), [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +Traits.variable_costate_trait(::AbstractFlow) = Traits.NoVariableCostate + +""" +$(TYPEDSIGNATURES) + +Return the variable costate capability trait of a Hamiltonian flow. + +# Returns +- `Type{<:AbstractVariableCostateCapability}`: The capability trait from the flow's system. + +# Notes +- Delegates to the system's variable costate trait via `Common.variable_costate_trait(system(flow))` +- This enables dispatch based on whether the flow's system can compute โˆ‚H/โˆ‚v + +See also: [`CTFlows.Traits.AbstractVariableCostateCapability`](@ref), [`CTFlows.Traits.variable_costate_trait`](@ref), [`CTFlows.Systems.AbstractHamiltonianSystem`](@ref). +""" +Traits.variable_costate_trait(f::AbstractHamiltonianFlow) = Traits.variable_costate_trait(system(f)) + +""" +$(TYPEDSIGNATURES) + +Return the associated `AbstractSystem` for the flow. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref). +""" +function system(flow::AbstractFlow) + throw(Exceptions.NotImplemented( + "AbstractFlow system method not implemented"; + required_method = "system(flow::$(typeof(flow)))", + suggestion = "Return the AbstractSystem associated with this flow.", + context = "AbstractFlow.system - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the associated `AbstractIntegrator` for the flow. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Integrators.AbstractIntegrator`](@ref). +""" +function integrator(flow::AbstractFlow) + throw(Exceptions.NotImplemented( + "AbstractFlow integrator method not implemented"; + required_method = "integrator(flow::$(typeof(flow)))", + suggestion = "Return the AbstractIntegrator associated with this flow.", + context = "AbstractFlow.integrator - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Display the flow in tree-style format with proper indentation for multi-line system displays. + +# Example +```julia-repl +julia> using CTFlows.Flows + +julia> flow = Flow(system, integrator) +StateFlow + system: VectorFieldSystem + wraps: VectorField: autonomous, fixed (no variable), out-of-place + integrator: SciML (abstol = 1e-8, reltol = 1e-6) +``` +""" +function Base.show(io::IO, ::MIME"text/plain", flow::AbstractFlow) + sys = system(flow) + integ = integrator(flow) + + # "system:" and "integrator:" padded to the same width for column alignment + lbl_sys = " system: " + lbl_int = " integrator: " + + # Capture system display; indent continuation lines to sit under the first + sys_str = sprint(show, sys) + sys_display = _indent_continuation(sys_str, length(lbl_sys)) + + println(io, nameof(typeof(flow))) + println(io, lbl_sys, sys_display) + print(io, lbl_int, nameof(typeof(integ))) + _print_user_options(io, integ) +end + +""" +$(TYPEDSIGNATURES) + +Compact display of the flow. + +# Example +```julia-repl +julia> using CTFlows.Flows + +julia> flow = Flow(system, integrator) +Flow(system=FakeSystem(n_x=2, n_p=2), integrator=FakeIntegrator) +``` +""" +function Base.show(io::IO, flow::AbstractFlow) + sys = system(flow) + integ = integrator(flow) + print(io, nameof(typeof(flow)), "(") + parts = String[] + push!(parts, "system=$(sys)") + push!(parts, "integrator=$(nameof(typeof(integ)))") + print(io, join(parts, ", ")) + print(io, ")") +end + +# ============================================================================= +# Internal helpers for show +# ============================================================================= + +""" + _indent_continuation(s::String, n::Int) -> String + +Indent every line of a multiline string by `n` spaces, except the first line. + +# Arguments +- `s::String`: The multiline string to indent. +- `n::Int`: Number of spaces to indent continuation lines. + +# Returns +- `String`: The indented string. + +# Example +\`\`\`julia +_indent_continuation("line1\\nline2\\nline3", 4) # Returns "line1\\n line2\\n line3" +\`\`\` +""" +function _indent_continuation(s::String, n::Int) + pad = " " ^ n + lines = split(s, "\n") + return join((i == 1 ? l : pad * l for (i, l) in enumerate(lines)), "\n") +end + +""" + _print_user_options(io::IO, integ::Integrators.AbstractIntegrator) + +Print user-supplied integrator options inline: `(key = val, โ€ฆ)`. +Silently does nothing when no user options are set. + +# Arguments +- `io::IO`: The IO stream to write to. +- `integ::Integrators.AbstractIntegrator`: The integrator to inspect for user options. + +# Example +\`\`\`julia +# If user options are set: prints " (abstol = 1e-8, reltol = 1e-6)" +# If no user options: prints nothing +\`\`\` +""" +function _print_user_options(io::IO, integ::Integrators.AbstractIntegrator) + opts = CTSolvers.Strategies.options(integ) + user_opts = sort!( + [(k, CTSolvers.value(v)) for (k, v) in pairs(opts.options) + if CTSolvers.is_user(opts, k)]; + by = x -> string(x[1]), + ) + isempty(user_opts) && return + print(io, " (") + for (i, (k, v)) in enumerate(user_opts) + i > 1 && print(io, ", ") + print(io, k, " = ", v) + end + print(io, ")") +end diff --git a/src/Flows/building.jl b/src/Flows/building.jl new file mode 100644 index 00000000..58f56564 --- /dev/null +++ b/src/Flows/building.jl @@ -0,0 +1,119 @@ +""" +$(TYPEDSIGNATURES) + +High-level constructor for `Flow` from vector field data. + +This constructor builds a complete flow by: +1. Building a `VectorFieldSystem` from the vector field data +2. Building a `SciML` integrator with the given options +3. Routing options through the integrator's CTSolvers strategy +4. Combining them into a callable `Flow` + +# Arguments +- `data::CTFlows.Data.VectorField`: The vector field defining the system dynamics. +- `opts...`: Keyword options passed to the integrator's strategy. + +# Returns +- `CTFlows.Flows.Flow`: The complete flow ready for integration. + +# Example +\`\`\`julia +using CTFlows.Data, CTFlows.Flows, CTFlows.Common + +vf = Data.VectorField((t, x, v) -> x, Traits.Autonomous(), Traits.Fixed()) +flow = Flows.Flow(vf; reltol=1e-8) +\`\`\` + +See also: [`CTFlows.Flows.Flow`](@ref), [`CTFlows.Systems.build_system`](@ref), [`CTFlows.Integrators.build_integrator`](@ref). +""" +function Flow(data::Data.VectorField; opts...) + system = Systems.build_system(data) + integrator = Integrators.build_integrator(; opts...) + return build_flow(system, integrator) +end + +""" +$(TYPEDSIGNATURES) + +High-level constructor for `HamiltonianFlow` from Hamiltonian vector field data. + +This constructor builds a complete Hamiltonian flow by: +1. Building a `HamiltonianVectorFieldSystem` from the Hamiltonian vector field data +2. Building a `SciML` integrator with the given options +3. Routing options through the integrator's CTSolvers strategy +4. Combining them into a callable `HamiltonianFlow` + +# Arguments +- `data::CTFlows.Data.HamiltonianVectorField`: The Hamiltonian vector field defining the system dynamics. +- `opts...`: Keyword options passed to the integrator's strategy. + +# Returns +- `CTFlows.Flows.HamiltonianFlow`: The complete Hamiltonian flow ready for integration. + +# Example +```julia +using CTFlows.Data, CTFlows.Flows + +hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) +flow = Flows.Flow(hvf; reltol=1e-8) +``` + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref), [`CTFlows.Systems.build_system`](@ref), [`CTFlows.Integrators.build_integrator`](@ref). +""" +function Flow(data::Data.HamiltonianVectorField; opts...) + system = Systems.build_system(data) + integrator = Integrators.build_integrator(; opts...) + return build_flow(system, integrator) +end + +""" +$(TYPEDSIGNATURES) + +High-level constructor for `HamiltonianFlow` from a scalar Hamiltonian. + +This constructor builds a complete Hamiltonian flow by: +1. Routing keyword options to the appropriate strategy families (backend and integrator) +2. Building a concrete AD backend and integrator from the routed options +3. Building a `HamiltonianSystem` from the Hamiltonian and backend +4. Combining them into a callable `HamiltonianFlow` + +# Arguments +- `h::CTFlows.Data.AbstractHamiltonian`: The scalar Hamiltonian function. +- `kwargs...`: Keyword options passed to the backend and integrator strategies. + Options are automatically routed based on their names: + - Backend options (e.g., `ad_backend`, `prepare_cache`) โ†’ `:di` strategy + - Integrator options (e.g., `reltol`, `abstol`, `alg`) โ†’ `:sciml` strategy + +# Returns +- `CTFlows.Flows.HamiltonianFlow`: The complete Hamiltonian flow ready for integration. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If an option is unknown, ambiguous, + or routed to the wrong strategy. +- [`CTBase.Exceptions.ExtensionError`](@extref): If the `CTFlowsSciML` extension is not loaded + (required for `:sciml` strategy metadata). + +# Example +```julia +using CTFlows.Data, CTFlows.Flows + +h = Data.Hamiltonian((t, x, p, v) -> 0.5 * (x[1]^2 + p[1]^2); is_autonomous=true, is_variable=false) +flow = Flows.Flow(h; reltol=1e-8, ad_backend=ADTypes.AutoForwardDiff()) +# flow isa CTFlows.Flows.HamiltonianFlow +``` + +# Notes +- The state dimension is inferred from the Hamiltonian's signature. +- Use the `state_dimension` argument overload if explicit dimension is needed. +- Requires the `CTFlowsSciML` extension to be loaded for integrator options. + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref), [`CTFlows.Systems.build_system`](@ref), +[`_route_flow_options`](@ref), [`_build_flow_components`](@ref) +""" +function Flow(h::Data.AbstractHamiltonian; kwargs...) + routed = _route_flow_options(kwargs) + components = _build_flow_components(routed) + sys = Systems.build_system(h, components.backend) + return build_flow(sys, components.integrator) +end + diff --git a/src/Flows/calling.jl b/src/Flows/calling.jl new file mode 100644 index 00000000..37e95913 --- /dev/null +++ b/src/Flows/calling.jl @@ -0,0 +1,616 @@ +""" +$(TYPEDSIGNATURES) + +Convenience call for `StateFlow` with point configuration. + +Builds a `StatePointConfig` internally and calls the flow with it. + +# Arguments +- `f::StateFlow`: The state flow to integrate. +- `t0::Real`: Initial time. +- `x0`: Initial state vector. +- `tf::Real`: Final time. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The integrated solution (type varies by system). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = StateFlow(system, integrator) + +julia> sol = flow(0.0, [1.0, 0.0], 1.0) +\`\`\` + +See also: [`CTFlows.Configs.StatePointConfig`](@ref), [`CTFlows.Flows.call`](@ref). +""" +function (f::AbstractStateFlow)( + t0::Real, + x0, + tf::Real; + variable=Common.__variable(), + unsafe=Common.__unsafe(), +) + return call(f, Configs.StatePointConfig(t0, x0, tf); variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `HamiltonianFlow` with point configuration. + +Builds a `HamiltonianPointConfig` internally and calls the flow with it. + +# Arguments +- `f::HamiltonianFlow`: The Hamiltonian flow to integrate. +- `t0::Real`: Initial time. +- `x0`: Initial state vector. +- `p0`: Initial costate vector. +- `tf::Real`: Final time. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The integrated solution (type varies by system). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = HamiltonianFlow(system, integrator) + +julia> sol = flow(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) +\`\`\` + +See also: [`CTFlows.Configs.HamiltonianPointConfig`](@ref), [`CTFlows.Flows.call`](@ref). +""" +function (f::AbstractHamiltonianFlow)( + t0::Real, + x0, + p0, + tf::Real; + variable=Common.__variable(), + unsafe=Common.__unsafe(), + variable_costate::Bool=Common._variable_costate(), +) + config = Configs.HamiltonianPointConfig(t0, x0, p0, tf) + variable_costate && return call_variable_costate(f, config; variable=variable, unsafe=unsafe) + return call(f, config; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `StateFlow` with trajectory configuration. + +Builds a `StateTrajectoryConfig` internally and calls the flow with it. + +# Arguments +- `f::StateFlow`: The state flow to integrate. +- `tspan::Tuple{Real, Real}`: Time span as a tuple (t0, tf). +- `x0`: Initial state vector. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The integrated solution (type varies by system). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = StateFlow(system, integrator) + +julia> sol = flow((0.0, 1.0), [1.0, 0.0]) +\`\`\` + +See also: [`CTFlows.Configs.StateTrajectoryConfig`](@ref), [`CTFlows.Flows.call`](@ref). +""" +function (f::AbstractStateFlow)( + tspan::Tuple{Real, Real}, + x0; + variable=Common.__variable(), + unsafe=Common.__unsafe(), +) + return call(f, Configs.StateTrajectoryConfig(tspan, x0); variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `HamiltonianFlow` with trajectory configuration. + +Builds a `HamiltonianTrajectoryConfig` internally and calls the flow with it. + +# Arguments +- `f::HamiltonianFlow`: The Hamiltonian flow to integrate. +- `tspan::Tuple{Real, Real}`: Time span as a tuple (t0, tf). +- `x0`: Initial state vector. +- `p0`: Initial costate vector. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The integrated solution (type varies by system). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = HamiltonianFlow(system, integrator) + +julia> sol = flow((0.0, 1.0), [1.0, 0.0], [0.5, 0.3]) +\`\`\` + +See also: [`CTFlows.Configs.HamiltonianTrajectoryConfig`](@ref), [`CTFlows.Flows.call`](@ref). +""" +function (f::AbstractHamiltonianFlow)( + tspan::Tuple{Real, Real}, + x0, + p0; + variable=Common.__variable(), + unsafe=Common.__unsafe(), + variable_costate::Bool=Common._variable_costate(), +) + config = Configs.HamiltonianTrajectoryConfig(tspan, x0, p0) + variable_costate && return call_variable_costate(f, config; variable=variable, unsafe=unsafe) + return call(f, config; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Solve an ODE problem using a flow with trait-based dispatch on the variable parameter. + +This function dispatches to one of four specialized implementations based on: +1. The flow's `VariableDependence` trait (`Fixed` or `NonFixed`) +2. Whether the `variable` parameter was provided (`NotProvided` vs any other type) + +# Dispatch Rules +- **`Fixed` + `NotProvided`**: Variable not required, proceeds with `variable=nothing`. +- **`Fixed` + provided**: Throws `PreconditionError` (Fixed systems must not receive a variable). +- **`NonFixed` + provided**: Variable required, proceeds with the provided value. +- **`NonFixed` + `NotProvided`**: Throws `PreconditionError` (NonFixed systems require a variable). + +# Arguments +- `flow::CTFlows.Flows.AbstractFlow`: The flow to solve. +- `config::CTFlows.Configs.AbstractConfig`: The integration configuration (e.g., `StatePointConfig`, `StateTrajectoryConfig`). +- `variable`: The variable parameter value (required for NonFixed systems, must be omitted for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The packaged solution (type varies by config type). + +# Throws +- `CTBase.Exceptions.PreconditionError`: If the variable parameter violates the flow's trait contract. + +# Example +\`\`\`julia +# Fixed flow: no variable parameter allowed +flow_fixed = Flow(system_fixed, integrator) +config = CTFlows.Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) +sol = call(flow_fixed, config; unsafe=false) # OK, no variable + +# NonFixed flow: variable parameter required +flow_nonfixed = Flow(system_nonfixed, integrator) +sol = call(flow_nonfixed, config; variable=0.5, unsafe=false) # OK, variable provided +\`\`\` + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Configs.VariableDependence`](@ref), [`CTFlows.Common.NotProvided`](@ref), [`CTFlows.Integrators.build_problem`](@ref), [`CTFlows.Integrators.solve_problem`](@ref), [`CTFlows.Solutions.build_solution`](@ref). +""" +function call(flow::Flows.AbstractFlow, config::Configs.AbstractConfig; variable, unsafe) + VD = Traits.variable_dependence(flow) + return call(VD, typeof(variable), flow, config; variable=variable, unsafe=unsafe) +end + +# ============================================================================= +# core_call โ€” implementation body (renamed from call) +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Internal implementation body for flow integration. + +This function performs the actual ODE integration workflow after trait-based +dispatch has validated the variable parameter. It extracts the system and +integrator from the flow, prepares cache, builds the ODE problem, solves it, +and constructs the solution. + +# Arguments +- `flow::CTFlows.Flows.AbstractFlow`: The flow to solve. +- `config::CTFlows.Configs.AbstractConfig`: The integration configuration. +- `variable`: The variable parameter value (may be `nothing` for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking. + +# Returns +- The packaged solution (type varies by config type). + +# Notes +This is an internal function called by the trait-dispatch overloads of `call`. +Users should call the public `call` function instead. + +See also: [`call`](@ref), [`CTFlows.Integrators.build_problem`](@ref), [`CTFlows.Integrators.solve_problem`](@ref), [`CTFlows.Solutions.build_solution`](@ref). +""" +function core_call(flow::Flows.AbstractFlow, config::Configs.AbstractConfig; variable, unsafe) + + # get system and integrator + sys = system(flow) + int = integrator(flow) + + # prepare cache for Hamiltonian systems (returns nothing for WithoutAD) + cache = prepare_cache(sys, config; variable=variable) + + # build ode problem + prob = Integrators.build_problem(int, sys, config; variable=variable, cache=cache) + + # build config-specific options + opts = Integrators.build_options(int, config) + + # integrate ode problem + result = Integrators.solve_problem(int, prob, opts; unsafe=unsafe) + + # build flow solution + flow_sol = Solutions.build_solution( + Configs.mode_trait(config), + Configs.content_trait(config), + config, + result, + ) + + return flow_sol +end + +# ============================================================================= +# call trait-dispatch overloads +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Dispatch for `NonFixed` flows when the variable parameter was not provided. + +This overload is selected when a `NonFixed` flow (which requires a variable) is called +without providing the `variable` argument. It throws a `PreconditionError` to enforce +the contract that NonFixed systems must receive a variable parameter. + +# Throws +- `CTBase.Exceptions.PreconditionError`: Always, with message explaining that a variable is required. + +# See also +[`call`](@ref), [`Configs.NonFixed`](@ref), [`Common.NotProvided`](@ref). +""" +function call(::Type{Traits.NonFixed}, ::Type{Common.NotProvided}, flow, config; unsafe, variable) + throw(Exceptions.PreconditionError( + "variable not provided for a NonFixed flow"; + reason = "flow depends on an extra variable parameter but none was given", + suggestion = "Pass `variable=v` when calling the flow", + context = "call โ€” NonFixed flow with missing variable", + )) +end + +""" +$(TYPEDSIGNATURES) + +Dispatch for `Fixed` flows when the variable parameter was not provided. + +This overload is selected when a `Fixed` flow (which does not require a variable) is called +without providing the `variable` argument. This is the expected and valid case, so it +forwards to `core_call` with `variable=nothing`. + +# Returns +- The result of `core_call`. + +# See also +[`call`](@ref), [`Configs.Fixed`](@ref), [`Common.NotProvided`](@ref), [`core_call`](@ref). +""" +function call(::Type{Traits.Fixed}, ::Type{Common.NotProvided}, flow, config; unsafe, variable) + return core_call(flow, config; variable=nothing, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Dispatch for `NonFixed` flows when a variable parameter is provided. + +This overload is selected when a `NonFixed` flow (which requires a variable) is called +with a provided variable parameter. This is the expected and valid case, so it forwards +to `core_call` with the provided variable value. + +# Returns +- The result of `core_call`. + +# See also +[`call`](@ref), [`Configs.NonFixed`](@ref), [`core_call`](@ref). +""" +function call(::Type{Traits.NonFixed}, ::Type{VT}, flow, config; unsafe, variable) where {VT} + return core_call(flow, config; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Dispatch for `Fixed` flows when a variable parameter is provided. + +This overload is selected when a `Fixed` flow (which does not require a variable) is called +with a provided variable parameter. This violates the contract that Fixed systems must not +receive a variable parameter, so it throws a `PreconditionError`. + +# Throws +- `CTBase.Exceptions.PreconditionError`: Always, with message explaining that variables must not be provided to Fixed flows. + +# See also +[`call`](@ref), [`Configs.Fixed`](@ref). +""" +function call(::Type{Traits.Fixed}, ::Type{VT}, flow, config; unsafe, variable) where {VT} + throw(Exceptions.PreconditionError( + "variable provided for a Fixed flow"; + reason = "flow does not depend on any variable parameter", + suggestion = "Remove the `variable` keyword argument when calling this flow", + context = "call โ€” Fixed flow with unexpected variable", + )) +end + +# ============================================================================== +# prepare_cache โ€” cache preparation for Hamiltonian systems +# ============================================================================== + +function prepare_cache( + sys::Systems.AbstractSystem, + config::Configs.AbstractConfig; variable +) + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Prepare an AD cache for a Hamiltonian system based on its AD trait. + +This is the front-end entry point that delegates to trait-specific implementations. + +# Arguments +- `sys::Systems.AbstractHamiltonianSystem`: The Hamiltonian system. +- `config::Configs.AbstractConfig`: The integration configuration (provides typical x0, p0). +- `variable`: The variable parameter value (for type inference in cache preparation). + +# Returns +- `nothing` for `WithoutAD` systems (no cache needed). +- The backend-specific cache for `WithAD` systems (e.g., `DifferentiationInterfaceCache`). + +# Trait Dispatch +- `WithoutAD` โ†’ returns `nothing` (system carries HVF directly). +- `WithAD` โ†’ delegates to backend's `prepare_cache` with typical x0, p0. + +# Config Type +The `config` argument is typed as `AbstractConfig` (not restricted to `HamiltonianConfig`) +to support augmented configs like `AugmentedHamiltonianPointConfig`, which define +`initial_state` and `initial_costate` via their own getters. + +# See also +- [`CTFlows.Differentiation.prepare_cache`](@ref) +- [`CTFlows.Flows.call`](@ref) +""" +function prepare_cache( + sys::Systems.AbstractHamiltonianSystem, + config::Configs.AbstractConfig; variable +) + return prepare_cache(Traits.ad_trait(sys), sys, config; variable=variable) +end + +""" +$(TYPEDSIGNATURES) + +Cache preparation for systems without AD (`WithoutAD` trait). + +Returns `nothing` since these systems carry a pre-computed Hamiltonian vector field +and do not require automatic differentiation. + +# Arguments +- `::Type{Traits.WithoutAD}`: The `WithoutAD` trait (dispatch tag). +- `sys::Systems.AbstractHamiltonianSystem`: The Hamiltonian system. +- `config::Configs.AbstractConfig`: The integration configuration (unused). +- `variable`: The variable parameter value (unused). + +# Returns +- `nothing` + +# See also +- [`CTFlows.Flows.prepare_cache`](@ref) +""" +function prepare_cache( + ::Type{Traits.WithoutAD}, + sys::Systems.AbstractHamiltonianSystem, + config::Configs.AbstractConfig; variable +) + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Cache preparation for systems with AD (`WithAD` trait). + +Extracts typical initial state and costate from the config and delegates to the +backend's `prepare_cache` method to prepare gradient plans. + +# Arguments +- `::Type{Traits.WithAD}`: The `WithAD` trait (dispatch tag). +- `sys::Systems.HamiltonianSystem`: The Hamiltonian system (carries `backend` and `h`). +- `config::Configs.AbstractConfig`: The integration configuration (provides x0, p0). +- `variable`: The variable parameter value (passed to backend for type inference). + +# Returns +- Backend-specific cache (e.g., `DifferentiationInterfaceCache` from the extension). + +# See also +- [`CTFlows.Differentiation.prepare_cache`](@ref) +- [`CTFlows.Flows.prepare_cache`](@ref) +""" +function prepare_cache( + ::Type{Traits.WithAD}, + sys::Systems.AbstractHamiltonianSystem, + config::Configs.AbstractConfig; variable +) + t0 = Configs.initial_time(config) + x0 = Configs.initial_state(config) + p0 = Configs.initial_costate(config) + return Differentiation.prepare_cache(Systems.backend(sys), Systems.hamiltonian(sys), t0, x0, p0, variable) +end + +# ============================================================================= +# call_variable_costate โ€” augmented Hamiltonian integration +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Call a Hamiltonian flow with variable costate integration. + +Dispatches on the flow's `variable_costate_trait` to determine if augmented +integration is supported. + +# Arguments +- `flow::AbstractHamiltonianFlow`: The Hamiltonian flow. +- `config::Configs.AbstractHamiltonianConfig`: The Hamiltonian point configuration. +- `variable`: The variable parameter value. +- `unsafe`: If `true`, bypass ODE solver retcode checking. + +# Returns +- The augmented solution `(xf, pf, pvf)` if supported, or throws an error. + +# Throws +- `CTBase.Exceptions.IncorrectArgument`: If the flow does not support variable costate. + +See also: [`CTFlows.Traits.variable_costate_trait`](@ref), [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +function call_variable_costate( + flow::AbstractHamiltonianFlow, + config::Configs.AbstractHamiltonianConfig; variable, unsafe +) + return call_variable_costate( + Traits.variable_costate_trait(flow), + typeof(variable), + flow, config; variable=variable, unsafe=unsafe + ) +end + +""" +$(TYPEDSIGNATURES) + +Variable costate call for flows that do not support it. + +This method handles the error case where a flow does not support variable costate computation +(typically because the Hamiltonian is not variable-dependent). + +# Throws +- `CTBase.Exceptions.PreconditionError`: Always, with a descriptive message indicating that the flow does not support variable costate. + +See also: [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +function call_variable_costate( + ::Type{Traits.NoVariableCostate}, + ::Type{VT}, + flow::AbstractHamiltonianFlow, + config::Configs.HamiltonianPointConfig; variable, unsafe +) where {VT} + throw(Exceptions.PreconditionError( + "variable_costate=true is not supported on this flow"; + reason = "Only flows built from a variable-dependent scalar Hamiltonian support it", + suggestion = "Use a Hamiltonian with is_variable=true and an AD backend", + context = "HamiltonianFlow call with variable_costate=true", + )) +end + +""" +$(TYPEDSIGNATURES) + +Variable costate call for flows that support it. + +Constructs an `AugmentedHamiltonianPointConfig` with zero initial variable costate +and calls the flow with it. + +# Arguments +- `::Type{Traits.SupportsVariableCostate}`: The capability trait. +- `flow::AbstractHamiltonianFlow`: The Hamiltonian flow. +- `config::Configs.HamiltonianPointConfig`: The base Hamiltonian point configuration. +- `variable`: The variable parameter value. +- `unsafe`: If `true`, bypass ODE solver retcode checking. + +# Returns +- `Tuple{AbstractVector, AbstractVector, AbstractVector}`: The augmented solution `(xf, pf, pvf)`. + +See also: [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Configs.AugmentedHamiltonianPointConfig`](@ref). +""" +function call_variable_costate( + ::Type{Traits.SupportsVariableCostate}, + ::Type{VT}, + flow::AbstractHamiltonianFlow, + config::Configs.HamiltonianPointConfig; variable, unsafe +) where {VT} + t0 = Configs.initial_time(config) + x0 = Configs.initial_state(config) + p0 = Configs.initial_costate(config) + pv0 = Common.scalarize(zeros(eltype(x0), length(variable)), variable) + tf = Configs.final_time(config) + config_aug = Configs.AugmentedHamiltonianPointConfig(t0, x0, p0, pv0, tf) + return call(flow, config_aug; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Variable costate call when the variable parameter is not provided. + +This method handles the error case where a flow supports variable costate computation +but the user did not provide the required variable parameter. + +# Throws +- `CTBase.Exceptions.PreconditionError`: Always, with a descriptive message indicating that the variable parameter must be provided. + +See also: [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Common.NotProvided`](@ref). +""" +function call_variable_costate( + ::Type{Traits.SupportsVariableCostate}, + ::Type{Common.NotProvided}, + flow::AbstractHamiltonianFlow, + config::Configs.HamiltonianPointConfig; variable, unsafe +) + throw(Exceptions.PreconditionError( + "variable must be provided when variable_costate=true"; + reason = "The system supports variable costate computation, which requires the variable parameter", + suggestion = "Provide the variable parameter, e.g., flow(t0, x0, p0, tf; variable=v, variable_costate=true)", + context = "HamiltonianFlow call with variable_costate=true but no variable provided", + )) +end + +""" +$(TYPEDSIGNATURES) + +Variable costate call for trajectory configurations. + +Variable costate computation is only supported for point configurations, not trajectory +configurations. This method throws an error when attempting to use variable_costate=true +with a trajectory configuration. + +# Throws +- `CTBase.Exceptions.PreconditionError`: Always, with a descriptive message indicating that variable_costate is only supported for point configurations. + +See also: [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Configs.HamiltonianTrajectoryConfig`](@ref). +""" +function call_variable_costate( + ::Type{Traits.SupportsVariableCostate}, + ::Type{VT}, + flow::AbstractHamiltonianFlow, + config::Configs.HamiltonianTrajectoryConfig; variable, unsafe +) where {VT} + throw(Exceptions.PreconditionError( + "variable_costate=true is not supported for trajectory configurations"; + reason = "Variable costate computation is only implemented for point configurations, not full trajectories", + suggestion = "Use variable_costate=true with a point configuration (t0, x0, p0, tf) instead of a trajectory configuration (tspan, x0, p0)", + context = "HamiltonianFlow call with variable_costate=true and HamiltonianTrajectoryConfig", + )) +end \ No newline at end of file diff --git a/src/Flows/flow.jl b/src/Flows/flow.jl new file mode 100644 index 00000000..d1e9d1c1 --- /dev/null +++ b/src/Flows/flow.jl @@ -0,0 +1,241 @@ +""" +$(TYPEDEF) + +Concrete flow for state systems (non-Hamiltonian). + +Combines a state system with an integrator for integration. The type parameters +encode the time-dependence and variable-dependence traits for compile-time dispatch. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `S <: AbstractStateSystem{TD, VD}`: The state system type +- `I <: AbstractIntegrator`: The integrator type + +# Fields +- `system::S`: The state system to integrate +- `integrator::I`: The integrator to use for integration + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows, CTFlows.Systems, CTFlows.Integrators + +julia> system = VectorFieldSystem(VectorField(x -> -x)) + +julia> integrator = SciML() + +julia> flow = StateFlow(system, integrator) +StateFlow{...} +\`\`\` + +See also: [`CTFlows.Flows.AbstractStateFlow`](@ref), [`CTFlows.Flows.HamiltonianFlow`](@ref). +""" +struct StateFlow{TD, VD, S<:Systems.AbstractStateSystem{TD, VD}, I<:Integrators.AbstractIntegrator} <: AbstractStateFlow{TD, VD, S} + system::S + integrator::I +end + +""" +$(TYPEDEF) + +Concrete flow for Hamiltonian systems. + +Combines a Hamiltonian system with an integrator for integration. The type parameters +encode the time-dependence and variable-dependence traits for compile-time dispatch. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `S <: AbstractHamiltonianSystem{TD, VD}`: The Hamiltonian system type +- `I <: AbstractIntegrator`: The integrator type + +# Fields +- `system::S`: The Hamiltonian system to integrate +- `integrator::I`: The integrator to use for integration + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows, CTFlows.Systems, CTFlows.Integrators + +julia> system = HamiltonianVectorFieldSystem(VectorField(x -> -x), VectorField(p -> -p)) + +julia> integrator = SciML() + +julia> flow = HamiltonianFlow(system, integrator) +HamiltonianFlow{...} +\`\`\` + +See also: [`CTFlows.Flows.AbstractHamiltonianFlow`](@ref), [`CTFlows.Flows.StateFlow`](@ref). +""" +struct HamiltonianFlow{TD, VD, S<:Systems.AbstractHamiltonianSystem{TD, VD}, I<:Integrators.AbstractIntegrator} <: AbstractHamiltonianFlow{TD, VD, S} + system::S + integrator::I +end + +""" +$(TYPEDSIGNATURES) + +Build a `StateFlow` from a state system and an integrator. + +# Arguments +- `system::S`: The state system to integrate. +- `integrator::I`: The integrator to use for integration. + +# Returns +- `StateFlow`: A concrete state flow combining the system and integrator. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows, CTFlows.Systems, CTFlows.Integrators + +julia> system = VectorFieldSystem(VectorField(x -> -x)) + +julia> integrator = SciML() + +julia> flow = build_flow(system, integrator) +StateFlow{...} +\`\`\` + +See also: [`CTFlows.Flows.StateFlow`](@ref), [`CTFlows.Flows.build_flow(::AbstractHamiltonianSystem, ::AbstractIntegrator)`](@ref). +""" +function build_flow(system::S, integrator::I) where {S<:Systems.AbstractStateSystem, I<:Integrators.AbstractIntegrator} + return StateFlow(system, integrator) +end + +""" +$(TYPEDSIGNATURES) + +Build a `HamiltonianFlow` from a Hamiltonian system and an integrator. + +# Arguments +- `system::S`: The Hamiltonian system to integrate. +- `integrator::I`: The integrator to use for integration. + +# Returns +- `HamiltonianFlow`: A concrete Hamiltonian flow combining the system and integrator. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows, CTFlows.Systems, CTFlows.Integrators + +julia> system = HamiltonianVectorFieldSystem(VectorField(x -> -x), VectorField(p -> -p)) + +julia> integrator = SciML() + +julia> flow = build_flow(system, integrator) +HamiltonianFlow{...} +\`\`\` + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref), [`CTFlows.Flows.build_flow(::AbstractStateSystem, ::AbstractIntegrator)`](@ref). +""" +function build_flow(system::S, integrator::I) where {S<:Systems.AbstractHamiltonianSystem, I<:Integrators.AbstractIntegrator} + return HamiltonianFlow(system, integrator) +end + +""" +$(TYPEDSIGNATURES) + +Return the system associated with a `StateFlow`. + +# Arguments +- `f::StateFlow`: The state flow. + +# Returns +- `S`: The state system stored in the flow. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = StateFlow(system, integrator) + +julia> system(flow) === system +true +\`\`\` + +See also: [`CTFlows.Flows.StateFlow`](@ref), [`CTFlows.Flows.integrator`](@ref). +""" +function system(f::StateFlow{TD, VD, S, I})::S where {TD, VD, S, I} + return f.system +end + +""" +$(TYPEDSIGNATURES) + +Return the system associated with a `HamiltonianFlow`. + +# Arguments +- `f::HamiltonianFlow`: The Hamiltonian flow. + +# Returns +- `S`: The Hamiltonian system stored in the flow. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = HamiltonianFlow(system, integrator) + +julia> system(flow) === system +true +\`\`\` + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref), [`CTFlows.Flows.integrator`](@ref). +""" +function system(f::HamiltonianFlow{TD, VD, S, I})::S where {TD, VD, S, I} + return f.system +end + +""" +$(TYPEDSIGNATURES) + +Return the integrator associated with a `StateFlow`. + +# Arguments +- `f::StateFlow`: The state flow. + +# Returns +- `I`: The integrator stored in the flow. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = StateFlow(system, integrator) + +julia> integrator(flow) === integrator +true +\`\`\` + +See also: [`CTFlows.Flows.StateFlow`](@ref), [`CTFlows.Flows.system`](@ref). +""" +function integrator(f::StateFlow{TD, VD, S, I})::I where {TD, VD, S, I} + return f.integrator +end + +""" +$(TYPEDSIGNATURES) + +Return the integrator associated with a `HamiltonianFlow`. + +# Arguments +- `f::HamiltonianFlow`: The Hamiltonian flow. + +# Returns +- `I`: The integrator stored in the flow. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> flow = HamiltonianFlow(system, integrator) + +julia> integrator(flow) === integrator +true +\`\`\` + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref), [`CTFlows.Flows.system`](@ref). +""" +function integrator(f::HamiltonianFlow{TD, VD, S, I})::I where {TD, VD, S, I} + return f.integrator +end diff --git a/src/Flows/flow_routing.jl b/src/Flows/flow_routing.jl new file mode 100644 index 00000000..1fec782b --- /dev/null +++ b/src/Flows/flow_routing.jl @@ -0,0 +1,119 @@ +""" +$(TYPEDSIGNATURES) + +Return the strategy families used for option routing in flow construction. + +The returned `NamedTuple` maps family names to their abstract types, as expected +by [`CTSolvers.Orchestration.route_all_options`](@extref). + +# Returns +- `NamedTuple`: `(backend, integrator)` mapped to their abstract types + +# Example +```julia +# Get the strategy families for flow construction +fam = Flows._flow_families() +# Returns: (backend = CTFlows.Differentiation.AbstractADBackend, integrator = CTFlows.Integrators.AbstractIntegrator) +``` + +See also: [`_route_flow_options`](@ref), [`flow_registry`](@ref) +""" +function _flow_families() + return ( + backend = Differentiation.AbstractADBackend, + integrator = Integrators.AbstractIntegrator, + ) +end + +const _FLOW_DESCRIPTION = (:di, :sciml) + +""" +$(TYPEDSIGNATURES) + +Route all keyword options to the appropriate strategy families for flow construction. + +This function wraps [`CTSolvers.Orchestration.route_all_options`](@extref) with the +families specific to CTFlows flow construction. Options are routed to either the +backend family (`:di`) or the integrator family (`:sciml`). + +# Arguments +- `kwargs`: All keyword arguments from the user's `Flow` call (strategy options only, + no action-level options). + +# Returns +- `NamedTuple` with fields: + - `action`: action-level options (always empty for flows) + - `strategies`: `NamedTuple` with `backend` and `integrator` sub-tuples + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If an option is unknown, ambiguous, + or routed to the wrong strategy. + +# Example +```julia +# Route options to backend and integrator +routed = Flows._route_flow_options((; reltol=1e-8, ad_backend=ADTypes.AutoForwardDiff())) +# routed.strategies.integrator contains (reltol = 1e-8,) +# routed.strategies.backend contains (ad_backend = AutoForwardDiff(),) +``` + +# Notes +- This function uses `:description` source mode for user-friendly error messages. +- No action-level options are defined for flows (empty `OptionDefinition` array). + +See also: [`_flow_families`](@ref), [`_build_flow_components`](@ref), +[`CTSolvers.Orchestration.route_all_options`](@extref) +""" +function _route_flow_options(kwargs) + return CTSolvers.Orchestration.route_all_options( + _FLOW_DESCRIPTION, + _flow_families(), + CTSolvers.Options.OptionDefinition[], + (; kwargs...), + flow_registry(); + source_mode = :description, + ) +end + +""" +$(TYPEDSIGNATURES) + +Build concrete strategy instances from routed options. + +Each strategy is constructed via +[`CTSolvers.Orchestration.build_strategy_from_resolved`](@extref) using the options +that were routed to its family by [`_route_flow_options`](@ref). + +# Arguments +- `routed`: Result of [`_route_flow_options`](@ref) containing routed option values. + +# Returns +- `NamedTuple{(:backend, :integrator)}`: Concrete strategy instances. + +# Example +```julia +# Build concrete strategies from routed options +routed = Flows._route_flow_options((; reltol=1e-8)) +components = Flows._build_flow_components(routed) +# components.backend isa CTFlows.Differentiation.DifferentiationInterface +# components.integrator isa CTFlows.Integrators.SciML +``` + +See also: [`_route_flow_options`](@ref), [`flow_registry`](@ref), +[`CTSolvers.Orchestration.build_strategy_from_resolved`](@extref) +""" +function _build_flow_components(routed) + families = _flow_families() + resolved = CTSolvers.Orchestration.resolve_method( + _FLOW_DESCRIPTION, families, flow_registry() + ) + backend = CTSolvers.Orchestration.build_strategy_from_resolved( + resolved, :backend, families, flow_registry(); + routed.strategies.backend... + ) + integrator = CTSolvers.Orchestration.build_strategy_from_resolved( + resolved, :integrator, families, flow_registry(); + routed.strategies.integrator... + ) + return (backend = backend, integrator = integrator) +end diff --git a/src/Flows/registry.jl b/src/Flows/registry.jl new file mode 100644 index 00000000..860f11a6 --- /dev/null +++ b/src/Flows/registry.jl @@ -0,0 +1,28 @@ +const _FLOW_REGISTRY = CTSolvers.Strategies.create_registry( + Differentiation.AbstractADBackend => (Differentiation.DifferentiationInterface,), + Integrators.AbstractIntegrator => (Integrators.SciML,), +) + +""" +$(TYPEDSIGNATURES) + +Return the strategy registry for flow construction. + +The registry maps abstract strategy families to their concrete implementations +for automatic differentiation backends and ODE integrators. + +# Returns +- `CTSolvers.Strategies.StrategyRegistry`: Registry with `:di` (DifferentiationInterface) + and `:sciml` (SciML) strategies registered. + +# Notes +- This registry is used by [`_route_flow_options`](@ref) to resolve and build + concrete strategy instances from keyword arguments. +- The registry is precomputed and cached in `_FLOW_REGISTRY` for performance. + +See also: [`_route_flow_options`](@ref), [`_build_flow_components`](@ref), +[`CTSolvers.Strategies.create_registry`](@extref) +""" +function flow_registry() + return _FLOW_REGISTRY +end diff --git a/src/Integrators/Integrators.jl b/src/Integrators/Integrators.jl new file mode 100644 index 00000000..1613acf4 --- /dev/null +++ b/src/Integrators/Integrators.jl @@ -0,0 +1,45 @@ +""" + Integrators + +ODE integrator strategy types for CTFlows. + +This module defines the `AbstractIntegrator` type which inherits from +`CTSolvers.Strategies.AbstractStrategy`. +""" +module Integrators + +# ============================================================================== +# External package imports +# ============================================================================== + +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions + +# ============================================================================== +# Internal sibling-submodule imports +# ============================================================================== + +import ..Common: Common +import ..Configs: Configs +import ..Systems: Systems +using CTSolvers: CTSolvers + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "integration_result.jl")) +include(joinpath(@__DIR__, "abstract_integrator.jl")) +include(joinpath(@__DIR__, "sciml.jl")) +include(joinpath(@__DIR__, "building.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractIntegrator, SciML, SciMLTag, Tsit5Tag +export AbstractIntegrationResult, final_state, times, evaluate_at +export build_sciml_integrator, build_integrator +export build_problem, solve_problem, merge + +end # module Integrators diff --git a/src/Integrators/abstract_integrator.jl b/src/Integrators/abstract_integrator.jl new file mode 100644 index 00000000..77bc52df --- /dev/null +++ b/src/Integrators/abstract_integrator.jl @@ -0,0 +1,149 @@ +""" +$(TYPEDEF) + +Abstract strategy for solving ODE Cauchy problems. + +An `AbstractIntegrator` is a strategy that solves an ODE problem over a time span. + +This type inherits the CTSolvers strategy contract: + +# Type-Level Contract (Static Metadata) + +Methods defined on the **type** that describe what the integrator can do: +- `id(::Type{<:S}) โ†’ Symbol`: Unique identifier for routing and introspection +- `metadata(::Type{<:S}) โ†’ StrategyMetadata`: Option specifications and validation rules + +# Instance-Level Contract (Configured State) + +Methods defined on **instances** that provide the actual configuration: +- `options(s::S) โ†’ StrategyOptions`: Current option values with provenance tracking + +# Concrete Implementation + +All subtypes must implement three named functions: + +- `build_problem(integrator::AbstractIntegrator, system::CTFlows.Systems.AbstractSystem, config::CTFlows.Configs.AbstractConfig; variable, cache)`: Build the ODE problem representation from a system and configuration. +- `build_options(integrator::AbstractIntegrator, config::Union{CTFlows.Configs.AbstractConfig, Nothing})`: Build solver options dict for the given configuration. +- `solve_problem(integrator::AbstractIntegrator, prob, options::Dict{Symbol,Any})`: Solve the given ODE problem with resolved options (tspan is embedded in `prob`). + +Additionally, for multi-phase trajectory support, subtypes should implement: + +- `merge(segments::AbstractVector{<:CTFlows.Integrators.AbstractIntegrationResult})`: Merge a sequence of integration results into a single result (used for concatenating multi-phase trajectories). + +# Throws +- `CTBase.Exceptions.NotImplemented`: If the methods are not implemented by the concrete type. + +See also: [`CTFlows.Flows.AbstractFlow`](@ref). +""" +abstract type AbstractIntegrator <: CTSolvers.Strategies.AbstractStrategy end + +""" +$(TYPEDSIGNATURES) + +Build the ODE problem representation from a system and configuration. + +# Arguments +- `integrator::AbstractIntegrator`: The integrator strategy. +- `system::CTFlows.Systems.AbstractSystem`: The system to build a problem for. +- `config::CTFlows.Configs.AbstractConfig`: The integration configuration. +- `variable`: The variable parameter value (required for NonFixed systems). +- `cache`: The cache for Hamiltonian systems (returns nothing for WithoutAD). + +# Returns +- The ODE problem representation (type varies by concrete integrator). + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.solve_problem`](@ref). +""" +function build_problem(integrator::AbstractIntegrator, system::Systems.AbstractSystem, config::Configs.AbstractConfig; variable, cache) + throw(Exceptions.NotImplemented( + "AbstractIntegrator problem building not implemented"; + required_method = "build_problem(integrator::$(typeof(integrator)), system::Systems.AbstractSystem, config::Configs.AbstractConfig; variable, cache)", + suggestion = "Implement build_problem(i::YourIntegrator, system, config; variable, cache) returning an ODE problem representation.", + context = "AbstractIntegrator problem building - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Solve the given ODE problem with resolved options. + +# Arguments +- `integrator::AbstractIntegrator`: The integrator strategy. +- `prob`: The ODE problem to solve (type varies by concrete integrator; tspan is embedded). +- `options::Dict{Symbol,Any}`: Resolved solver options (typically from `build_options`). +- `unsafe=Common.__unsafe()`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The ODE integration result, as a subtype of `CTFlows.Integrators.AbstractIntegrationResult`. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.build_problem`](@ref), [`CTFlows.Integrators.build_options`](@ref). +""" +function solve_problem(integrator::AbstractIntegrator, prob, options::Dict{Symbol,<:Any}; unsafe=Common.__unsafe()) + throw(Exceptions.NotImplemented( + "AbstractIntegrator solve_problem not implemented"; + required_method = "solve_problem(integrator::$(typeof(integrator)), prob, options::Dict{Symbol,Any}; unsafe=false)", + suggestion = "Implement solve_problem(i::YourIntegrator, prob, options::Dict; unsafe=false) returning an AbstractIntegrationResult.", + context = "AbstractIntegrator solve_problem - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Build solver options dict for the given configuration. + +# Arguments +- `integrator::AbstractIntegrator`: The integrator strategy. +- `config::Union{CTFlows.Configs.AbstractConfig, Nothing}`: The integration configuration (or `Nothing` for fallback). + +# Returns +- `Dict{Symbol,Any}`: Resolved solver options for the given configuration. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.build_problem`](@ref), [`CTFlows.Integrators.solve_problem`](@ref). +""" +function build_options(integrator::AbstractIntegrator, config::Union{Configs.AbstractConfig, Nothing}) + throw(Exceptions.NotImplemented( + "AbstractIntegrator build_options not implemented"; + required_method = "build_options(integrator::$(typeof(integrator)), config::Union{Configs.AbstractConfig, Nothing})", + suggestion = "Implement build_options(i::YourIntegrator, config) returning a Dict{Symbol,Any} of resolved solver options.", + context = "AbstractIntegrator build_options - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Merge a sequence of integration results into a single result. + +This is used for concatenating multi-phase trajectories. Concrete integrator types +should implement this method for their specific result types. + +# Arguments +- `segments::AbstractVector{T}`: Sequence of integration results to merge, where `T<:Integrators.AbstractIntegrationResult`. + +# Returns +- A single `AbstractIntegrationResult` representing the merged trajectory. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.AbstractIntegrationResult`](@ref). +""" +function merge(segments::AbstractVector{T}) where {T<:Integrators.AbstractIntegrationResult} + throw(Exceptions.NotImplemented( + "AbstractIntegrator merge not implemented"; + required_method = "merge(segments::Vector{<:$(T)})", + suggestion = "Implement merge(segments::Vector{<:YourIntegrationResult}) returning a merged YourIntegrationResult.", + context = "AbstractIntegrator merge - required method implementation for multi-phase trajectories", + )) +end diff --git a/src/Integrators/building.jl b/src/Integrators/building.jl new file mode 100644 index 00000000..d3d96db7 --- /dev/null +++ b/src/Integrators/building.jl @@ -0,0 +1,23 @@ +""" +$(TYPEDSIGNATURES) + +Build a `SciML` integrator with the given options. + +# Arguments +- `kwargs...`: Options forwarded to the `SciML` constructor. + +# Returns +- `CTFlows.Integrators.SciML`: The configured integrator. + +# Example +\`\`\`julia +using CTFlows.Integrators + +integrator = Integrators.build_integrator(reltol=1e-8, alg=Tsit5()) +\`\`\` + +See also: [`CTFlows.Integrators.SciML`](@ref), [`CTFlows.Integrators.build_sciml_integrator`](@ref). +""" +function build_integrator(; kwargs...) + return SciML(; kwargs...) +end diff --git a/src/Integrators/integration_result.jl b/src/Integrators/integration_result.jl new file mode 100644 index 00000000..ca08d794 --- /dev/null +++ b/src/Integrators/integration_result.jl @@ -0,0 +1,92 @@ +""" + integration_result.jl + +Provides the `AbstractIntegrationResult` abstraction for ODE integration output. +""" + +""" +$(TYPEDEF) + +Abstract supertype for integration results produced by integrators. + +This abstraction decouples the `Solutions` layer from the concrete types of the underlying +ODE solvers (e.g. SciML). Integrators must produce a subtype of `AbstractIntegrationResult` +which provides semantic accessors. + +# Interface Requirements + +Subtypes must implement: +- `final_state(r::SubType)`: Return the final state vector. +- `times(r::SubType)`: Return the vector of time points. +- `evaluate_at(r::SubType, t::Real)`: Evaluate the continuous solution at time `t`. + +See also: [`CTFlows.Integrators.final_state`](@ref), [`CTFlows.Integrators.times`](@ref), [`CTFlows.Integrators.evaluate_at`](@ref). +""" +abstract type AbstractIntegrationResult end + +""" +$(TYPEDSIGNATURES) + +Return the final state vector from the integration result. + +# Arguments +- `r::AbstractIntegrationResult`: The integration result. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Integrators.times`](@ref), [`CTFlows.Integrators.evaluate_at`](@ref). +""" +function final_state(r::AbstractIntegrationResult) + throw(Exceptions.NotImplemented( + "final_state not implemented"; + required_method = "final_state(r::$(typeof(r)))", + suggestion = "Implement final_state(r) for your integration result type.", + context = "AbstractIntegrationResult - final_state implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the vector of time points from the integration result. + +# Arguments +- `r::AbstractIntegrationResult`: The integration result. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Integrators.final_state`](@ref), [`CTFlows.Integrators.evaluate_at`](@ref). +""" +function times(r::AbstractIntegrationResult) + throw(Exceptions.NotImplemented( + "times not implemented"; + required_method = "times(r::$(typeof(r)))", + suggestion = "Implement times(r) for your integration result type.", + context = "AbstractIntegrationResult - times implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate the integration result at a specific time `t`. + +# Arguments +- `r::AbstractIntegrationResult`: The integration result. +- `t::Real`: The time at which to evaluate the solution. + +# Throws +- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Integrators.final_state`](@ref), [`CTFlows.Integrators.times`](@ref). +""" +function evaluate_at(r::AbstractIntegrationResult, t::Real) + throw(Exceptions.NotImplemented( + "evaluate_at not implemented"; + required_method = "evaluate_at(r::$(typeof(r)), t::Real)", + suggestion = "Implement evaluate_at(r, t) for your integration result type.", + context = "AbstractIntegrationResult - evaluate_at implementation", + )) +end diff --git a/src/Integrators/sciml.jl b/src/Integrators/sciml.jl new file mode 100644 index 00000000..c7a5caac --- /dev/null +++ b/src/Integrators/sciml.jl @@ -0,0 +1,171 @@ +""" +$(TYPEDEF) + +Tag type for SciML integrator dispatch. Used to target the implementation +provided by the `CTFlowsSciML` package extension. +""" +struct SciMLTag <: Common.AbstractTag end + +""" +$(TYPEDEF) + +Tag type for Tsit5-specific default algorithm dispatch. Used to target +the implementation provided by the `CTFlowsOrdinaryDiffEqTsit5` package extension. +""" +struct Tsit5Tag <: Common.AbstractTag end + +""" +$(TYPEDEF) + +Abstract supertype for SciML-based ODE integrator strategies. + +This type defines the interface for all integrator strategies that use SciML solvers. +Concrete subtypes should store strategy options and implement the required contract methods. + +# Interface Requirements + +Subtypes must implement: +- `CTSolvers.Strategies.id(::Type{<:SubType})`: Return unique identifier +- `CTSolvers.Strategies.description(::Type{<:SubType})`: Return description +- `CTSolvers.Strategies.metadata(::Type{<:SubType})`: Return option metadata + +# Example +```julia-repl +julia> using CTFlows.Integrators + +julia> SciML <: AbstractSciMLIntegrator +true +``` + +See also: [`Integrators.SciML`](@ref), [`Integrators.SciMLTag`](@ref). +""" +abstract type AbstractSciMLIntegrator <: AbstractIntegrator end + +""" +$(TYPEDEF) + +Generic SciML ODE integrator strategy. + +Wraps any SciML algorithm (e.g. `Tsit5`, `Rodas4`) through a unified +`CTSolvers`-backed option system. The full implementation (metadata, builder +and callable) is provided by the `CTFlowsSciML` package extension; this +file declares the type and **stubs** that throw `ExtensionError` until the +extension is loaded. + +To activate the extension, load any of: +- `using OrdinaryDiffEqTsit5` (minimal) +- `using OrdinaryDiffEq` +- `using DifferentialEquations` + +# Fields +- `options::CTSolvers.Strategies.StrategyOptions`: Validated option bundle. +- `options_point::Dict{Symbol, Any}`: Pre-computed options for StatePointConfig. +- `options_trajectory::Dict{Symbol, Any}`: Pre-computed options for StateTrajectoryConfig. +""" +struct SciML{O<:CTSolvers.Strategies.StrategyOptions, OP<:Dict{Symbol, Any}, OT<:Dict{Symbol, Any}} <: AbstractSciMLIntegrator + options::O + options_point::OP + options_trajectory::OT +end + +# ============================================================================ +# AbstractStrategy Contract Implementation +# ============================================================================ + +""" +$(TYPEDSIGNATURES) + +Return the unique identifier for SciML integrator. +""" +CTSolvers.Strategies.id(::Type{<:SciML}) = :sciml + +""" +$(TYPEDSIGNATURES) + +Return the description for the SciML integrator. +""" +function CTSolvers.Strategies.description(::Type{<:SciML}) + "SciML ODE integrator.\n" * + "See: https://docs.sciml.ai/DiffEqDocs\n" * + "Solver options: https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/" +end + +# ============================================================================ +# Constructor with Tag Dispatch +# ============================================================================ + +""" +$(TYPEDSIGNATURES) + +Construct a `SciML` integrator. Delegates to `build_sciml_integrator`, which +is overridden by the `CTFlowsSciML` package extension. + +# Arguments +- `kwargs...`: Options forwarded to the integrator builder (see extension documentation). + +# Throws +- `CTBase.Exceptions.ExtensionError`: If the CTFlowsSciML extension is not loaded. + +See also: `SciML`, `build_sciml_integrator`. +""" +function SciML(; mode::Symbol = :strict, kwargs...) + return build_sciml_integrator(SciMLTag; mode = mode, kwargs...) +end + +""" +$(TYPEDSIGNATURES) + +Stub builder for `SciML`. The real implementation is provided by +`CTFlowsSciML`; this stub throws `ExtensionError` until the extension +is loaded. +""" +function build_sciml_integrator(::Type{<:Common.AbstractTag}; kwargs...) + throw( + Exceptions.ExtensionError( + :OrdinaryDiffEqTsit5; + message = "to construct a SciML", + feature = "ODE integration via SciML", + context = "Load OrdinaryDiffEqTsit5, OrdinaryDiffEq, or DifferentialEquations to activate the CTFlowsSciML extension.", + ), + ) +end + +""" +$(TYPEDSIGNATURES) + +Stub function that throws ExtensionError if CTFlowsSciML extension is not loaded. +Real metadata implementation provided by the extension. + +# Throws +- `CTBase.Exceptions.ExtensionError`: Always thrown by this stub implementation + +See also: `SciML`, `CTSolvers.Strategies.StrategyMetadata`. +""" +function CTSolvers.Strategies.metadata(::Type{<:AbstractSciMLIntegrator}) + # Extension is missing + throw( + Exceptions.ExtensionError( + :OrdinaryDiffEqTsit5; + message="to access SciML options metadata", + feature="SciML metadata", + context="Load OrdinaryDiffEqTsit5, OrdinaryDiffEq, or DifferentialEquations extension first: using OrdinaryDiffEqTsit5", + ), + ) +end + +""" +$(TYPEDSIGNATURES) + +Return the default SciML ODE algorithm for the given tag type. + +This stub returns `missing` for the abstract tag type. The actual implementation +for Tsit5Tag is provided by CTFlowsOrdinaryDiffEqTsit5. + +# Returns +- `missing`: Default stub implementation. + +See also: [`Integrators.SciML`](@ref), [`Integrators.Tsit5Tag`](@ref). +""" +function __default_sciml_algorithm(::Type{<:Common.AbstractTag}) + return missing +end \ No newline at end of file diff --git a/src/MultiPhase/MultiPhase.jl b/src/MultiPhase/MultiPhase.jl new file mode 100644 index 00000000..a152ac09 --- /dev/null +++ b/src/MultiPhase/MultiPhase.jl @@ -0,0 +1,45 @@ +""" + MultiPhase + +Multi-phase flow concatenation and sequential integration. + +This module provides types and operators for concatenating flows with switching times +and optional jumps, implementing exact sequential integration. +""" +module MultiPhase + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +import CTSolvers.Strategies +import CTSolvers.Options + +# ============================================================================== +# Internal sibling-submodule imports +# ============================================================================== + +import ..Common: Common +import ..Configs: Configs +import ..Traits: Traits +import ..Systems: Systems +import ..Integrators: Integrators +import ..Flows: Flows +import ..Solutions: Solutions + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "multiphase_flow.jl")) +include(joinpath(@__DIR__, "concatenation.jl")) +include(joinpath(@__DIR__, "calling.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export MultiPhaseStateFlow, MultiPhaseHamiltonianFlow +export n_phases, get_flow, get_switching_time, get_jump +export get_flows, get_switching_times, get_jumps + +end # module MultiPhase diff --git a/src/MultiPhase/calling.jl b/src/MultiPhase/calling.jl new file mode 100644 index 00000000..419565cb --- /dev/null +++ b/src/MultiPhase/calling.jl @@ -0,0 +1,540 @@ +# ============================================================================== +# Generic Multiphase Evaluation Loop +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Extract the initial state from a state configuration. + +# Arguments +- `config::Configs.AbstractStateConfig`: The state configuration (StatePointConfig or StateTrajectoryConfig). + +# Returns +- Initial state vector. + +See also: [`CTFlows.Configs.initial_state`](@ref), [`CTFlows.Configs.AbstractStateConfig`](@ref). +""" +function _extract_initial_state(config::Configs.AbstractStateConfig) + return Configs.initial_state(config) +end + +""" +$(TYPEDSIGNATURES) + +Extract the initial state and costate from a Hamiltonian configuration. + +# Arguments +- `config::Configs.AbstractHamiltonianConfig`: The Hamiltonian configuration (HamiltonianPointConfig or HamiltonianTrajectoryConfig). + +# Returns +- Tuple of (initial_state, initial_costate). + +See also: [`CTFlows.Configs.initial_state`](@ref), [`CTFlows.Configs.initial_costate`](@ref), [`CTFlows.Configs.AbstractHamiltonianConfig`](@ref). +""" +function _extract_initial_state(config::Configs.AbstractHamiltonianConfig) + return (Configs.initial_state(config), Configs.initial_costate(config)) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate a multi-phase flow for a point configuration, returning only the final state. + +Iterates through all phases sequentially, applying jumps at switching times. + +# Arguments +- `mpf`: The multi-phase flow to evaluate. +- `config::Configs.AbstractPointConfig`: The point configuration with time span and initial conditions. +- `variable`: The variable parameter value (for NonFixed systems). +- `unsafe`: If true, bypass ODE solver retcode checking. + +# Returns +- Final state after all phases. + +See also: [`CTFlows.MultiPhase._evaluate_phase`](@ref), [`CTFlows.MultiPhase._apply_jump`](@ref). +""" +function _evaluate_multiphase(mpf, config::Configs.AbstractPointConfig; variable, unsafe) + t0, tf = Configs.tspan(config) + current_state = _extract_initial_state(config) + current_t = t0 + n_ph = n_phases(mpf) + + for i in 1:n_ph + t_end = (i < n_ph) ? get_switching_time(mpf, i) : tf + + current_state = _evaluate_phase(get_flow(mpf, i), current_t, t_end, current_state, config; variable=variable, unsafe=unsafe) + + current_t = t_end + + if i < n_ph + jump = get_jump(mpf, i) + if !isnothing(jump) + current_state = _apply_jump(mpf, i, current_state) + end + end + end + + return _format_final_output(mpf, current_state) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate a multi-phase flow for a trajectory configuration, returning the full trajectory. + +Iterates through all phases sequentially, collecting segment results and applying jumps at switching times. + +# Arguments +- `mpf`: The multi-phase flow to evaluate. +- `config::Configs.AbstractTrajectoryConfig`: The trajectory configuration with time span and initial conditions. +- `variable`: The variable parameter value (for NonFixed systems). +- `unsafe`: If true, bypass ODE solver retcode checking. + +# Returns +- Merged trajectory solution after all phases. + +See also: [`CTFlows.Integrators.merge`](@ref), [`CTFlows.MultiPhase._evaluate_phase`](@ref), [`CTFlows.MultiPhase._apply_jump`](@ref). +""" +function _evaluate_multiphase(mpf, config::Configs.AbstractTrajectoryConfig; variable, unsafe) + t0, tf = Configs.tspan(config) + current_state = _extract_initial_state(config) + current_t = t0 + n_ph = n_phases(mpf) + + results = nothing + + for i in 1:n_ph + t_end = (i < n_ph) ? get_switching_time(mpf, i) : tf + + segment_result = _evaluate_phase(get_flow(mpf, i), current_t, t_end, current_state, config; variable=variable, unsafe=unsafe) + results = isnothing(results) ? [segment_result] : push!(results, segment_result) + + current_state = _extract_final_state(mpf, segment_result, current_state) + current_t = t_end + + if i < n_ph + jump = get_jump(mpf, i) + if !isnothing(jump) + current_state = _apply_jump(mpf, i, current_state) + end + end + end + + return Integrators.merge(results) +end + +# ============================================================================== +# Phase Evaluation & Jump Delegation +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Evaluate a single phase for a state flow with point configuration. + +# Arguments +- `flow::Flows.StateFlow`: The state flow to evaluate. +- `t0`: Start time. +- `tf`: End time. +- `x`: Initial state. +- `::Configs.AbstractPointConfig`: Point configuration type tag. +- `variable`: The variable parameter value (for NonFixed systems). +- `unsafe`: If true, bypass ODE solver retcode checking. + +# Returns +- Final state at time tf. + +See also: [`CTFlows.Flows.StateFlow`](@ref). +""" +function _evaluate_phase(flow::Flows.StateFlow, t0, tf, x, ::Configs.AbstractPointConfig; variable, unsafe) + return flow(t0, x, tf; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate a single phase for a state flow with trajectory configuration. + +# Arguments +- `flow::Flows.StateFlow`: The state flow to evaluate. +- `t0`: Start time. +- `tf`: End time. +- `x`: Initial state. +- `::Configs.AbstractTrajectoryConfig`: Trajectory configuration type tag. +- `variable`: The variable parameter value (for NonFixed systems). +- `unsafe`: If true, bypass ODE solver retcode checking. + +# Returns +- Trajectory solution from t0 to tf. + +See also: [`CTFlows.Flows.StateFlow`](@ref). +""" +function _evaluate_phase(flow::Flows.StateFlow, t0, tf, x, ::Configs.AbstractTrajectoryConfig; variable, unsafe) + return flow((t0, tf), x; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate a single phase for a Hamiltonian flow with point configuration. + +# Arguments +- `flow::Flows.HamiltonianFlow`: The Hamiltonian flow to evaluate. +- `t0`: Start time. +- `tf`: End time. +- `state_tuple`: Tuple of (initial_state, initial_costate). +- `::Configs.AbstractPointConfig`: Point configuration type tag. +- `variable`: The variable parameter value (for NonFixed systems). +- `unsafe`: If true, bypass ODE solver retcode checking. + +# Returns +- Tuple of (final_state, final_costate) at time tf. + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref). +""" +function _evaluate_phase(flow::Flows.HamiltonianFlow, t0, tf, state_tuple, ::Configs.AbstractPointConfig; variable, unsafe) + x, p = state_tuple + return flow(t0, x, p, tf; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate a single phase for a Hamiltonian flow with trajectory configuration. + +# Arguments +- `flow::Flows.HamiltonianFlow`: The Hamiltonian flow to evaluate. +- `t0`: Start time. +- `tf`: End time. +- `state_tuple`: Tuple of (initial_state, initial_costate). +- `::Configs.AbstractTrajectoryConfig`: Trajectory configuration type tag. +- `variable`: The variable parameter value (for NonFixed systems). +- `unsafe`: If true, bypass ODE solver retcode checking. + +# Returns +- Trajectory solution from t0 to tf. + +See also: [`CTFlows.Flows.HamiltonianFlow`](@ref). +""" +function _evaluate_phase(flow::Flows.HamiltonianFlow, t0, tf, state_tuple, ::Configs.AbstractTrajectoryConfig; variable, unsafe) + x, p = state_tuple + return flow((t0, tf), x, p; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Extract the final state from a segment result for state flows. + +# Arguments +- `::MultiPhaseStateFlow`: Multi-phase state flow type tag. +- `segment`: The segment solution. +- `current_state`: Current state (unused for state flows). + +# Returns +- Final state from the segment. + +See also: [`CTFlows.Solutions.final_state`](@ref). +""" +function _extract_final_state(::MultiPhaseStateFlow, segment, current_state) + return Integrators.final_state(segment) +end + +""" +$(TYPEDSIGNATURES) + +Extract the final state and costate from a segment result for Hamiltonian flows. + +# Arguments +- `::MultiPhaseHamiltonianFlow`: Multi-phase Hamiltonian flow type tag. +- `segment`: The segment solution. +- `current_state`: Current state tuple (used to determine state dimension). + +# Returns +- Tuple of (final_state, final_costate) from the segment. + +See also: [`CTFlows.Solutions.final_state`](@ref). +""" +function _extract_final_state(::MultiPhaseHamiltonianFlow, segment, current_state) + final = Integrators.final_state(segment) + nx = length(current_state[1]) + return (final[1:nx], final[nx+1:end]) +end + +""" +$(TYPEDSIGNATURES) + +Apply a jump to the state for state flows. + +# Arguments +- `mpf::MultiPhaseStateFlow`: The multi-phase state flow. +- `i`: Phase index. +- `state`: Current state. + +# Returns +- State after applying the jump. + +See also: [`CTFlows.MultiPhase.get_jump`](@ref). +""" +function _apply_jump(mpf::MultiPhaseStateFlow, i, state) + jump = get_jump(mpf, i) + return state .+ jump +end + +""" +$(TYPEDSIGNATURES) + +Apply a jump to the state tuple for Hamiltonian flows. + +# Arguments +- `mpf::MultiPhaseHamiltonianFlow`: The multi-phase Hamiltonian flow. +- `i`: Phase index. +- `state_tuple`: Tuple of (state, costate). + +# Returns +- State tuple after applying the jump. + +See also: [`CTFlows.MultiPhase._apply_hamiltonian_jump`](@ref), [`CTFlows.MultiPhase.get_jump`](@ref). +""" +function _apply_jump(mpf::MultiPhaseHamiltonianFlow, i, state_tuple) + jump = get_jump(mpf, i) + return _apply_hamiltonian_jump(state_tuple, jump) +end + +""" +$(TYPEDSIGNATURES) + +Apply a tuple jump to a Hamiltonian state tuple. + +# Arguments +- `state_tuple::Tuple`: Tuple of (state, costate). +- `jump::Tuple`: Tuple of (state_jump, costate_jump). + +# Returns +- Tuple of (state + state_jump, costate + costate_jump). + +See also: [`CTFlows.MultiPhase._apply_jump`](@ref). +""" +function _apply_hamiltonian_jump(state_tuple::Tuple, jump::Tuple) + x, p = state_tuple + return (x + jump[1], p + jump[2]) +end + +""" +$(TYPEDSIGNATURES) + +Apply a scalar jump to the costate component of a Hamiltonian state tuple. + +# Arguments +- `state_tuple::Tuple`: Tuple of (state, costate). +- `jump`: Costate jump value (scalar). + +# Returns +- Tuple of (state, costate + jump). + +See also: [`CTFlows.MultiPhase._apply_jump`](@ref). +""" +function _apply_hamiltonian_jump(state_tuple::Tuple, jump) + x, p = state_tuple + return (x, p + jump) +end + +""" +$(TYPEDSIGNATURES) + +Format the final output for state flows. + +# Arguments +- `::MultiPhaseStateFlow`: Multi-phase state flow type tag. +- `x`: Final state. + +# Returns +- The final state (no formatting needed). + +See also: [`CTFlows.MultiPhase._evaluate_multiphase`](@ref). +""" +function _format_final_output(::MultiPhaseStateFlow, x) + return x +end + +""" +$(TYPEDSIGNATURES) + +Format the final output for Hamiltonian flows by concatenating state and costate. + +# Arguments +- `::MultiPhaseHamiltonianFlow`: Multi-phase Hamiltonian flow type tag. +- `state_tuple`: Tuple of (final_state, final_costate). + +# Returns +- Concatenated vector [state; costate]. + +See also: [`CTFlows.MultiPhase._evaluate_multiphase`](@ref). +""" +function _format_final_output(::MultiPhaseHamiltonianFlow, state_tuple) + x, p = state_tuple + return vcat(x, p) +end + +# ============================================================================== +# Public Callable Interfaces +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Convenience call for `MultiPhaseStateFlow` with point configuration. + +Builds a `StatePointConfig` internally and evaluates the multi-phase flow. + +# Arguments +- `mpf::MultiPhaseStateFlow`: The multi-phase state flow to evaluate. +- `t0::Real`: Initial time. +- `x0`: Initial state vector. +- `tf::Real`: Final time. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The final state after sequential integration through all phases. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +mpf = flow1 * (1.0, flow2) * (2.0, flow3) +sol = mpf(0.0, [1.0, 0.0], 3.0) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.Configs.StatePointConfig`](@ref). +""" +function (mpf::MultiPhaseStateFlow)( + t0::Real, + x0, + tf::Real; + variable=Common.__variable(), + unsafe=Common.__unsafe(), +) + config = Configs.StatePointConfig(t0, x0, tf) + return _evaluate_multiphase(mpf, config; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `MultiPhaseStateFlow` with trajectory configuration. + +Builds a `StateTrajectoryConfig` internally and evaluates the multi-phase flow, +returning a merged trajectory from all phases. + +# Arguments +- `mpf::MultiPhaseStateFlow`: The multi-phase state flow to evaluate. +- `tspan::Tuple{Real, Real}`: Time span as a tuple (t0, tf). +- `x0`: Initial state vector. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The merged trajectory solution after sequential integration through all phases. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +mpf = flow1 * (1.0, flow2) * (2.0, flow3) +sol = mpf((0.0, 3.0), [1.0, 0.0]) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.Configs.StateTrajectoryConfig`](@ref). +""" +function (mpf::MultiPhaseStateFlow)( + tspan::Tuple{Real, Real}, + x0; + variable=Common.__variable(), + unsafe=Common.__unsafe(), +) + config = Configs.StateTrajectoryConfig(tspan, x0) + return _evaluate_multiphase(mpf, config; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `MultiPhaseHamiltonianFlow` with point configuration. + +Builds a `HamiltonianPointConfig` internally and evaluates the multi-phase flow. + +# Arguments +- `mpf::MultiPhaseHamiltonianFlow`: The multi-phase Hamiltonian flow to evaluate. +- `t0::Real`: Initial time. +- `x0`: Initial state vector. +- `p0`: Initial costate vector. +- `tf::Real`: Final time. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The final state and costate after sequential integration through all phases. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +mpf = flow1 * (1.0, flow2) * (2.0, flow3) +sol = mpf(0.0, [1.0, 0.0], [0.5, 0.3], 3.0) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.Configs.HamiltonianPointConfig`](@ref). +""" +function (mpf::MultiPhaseHamiltonianFlow)( + t0::Real, + x0, + p0, + tf::Real; + variable=Common.__variable(), + unsafe=Common.__unsafe(), +) + config = Configs.HamiltonianPointConfig(t0, x0, p0, tf) + return _evaluate_multiphase(mpf, config; variable=variable, unsafe=unsafe) +end + +""" +$(TYPEDSIGNATURES) + +Convenience call for `MultiPhaseHamiltonianFlow` with trajectory configuration. + +Builds a `HamiltonianTrajectoryConfig` internally and evaluates the multi-phase flow, +returning a merged trajectory from all phases. + +# Arguments +- `mpf::MultiPhaseHamiltonianFlow`: The multi-phase Hamiltonian flow to evaluate. +- `tspan::Tuple{Real, Real}`: Time span as a tuple (t0, tf). +- `x0`: Initial state vector. +- `p0`: Initial costate vector. +- `variable`: The variable parameter value (required for NonFixed systems, optional for Fixed systems). +- `unsafe`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure. + +# Returns +- The merged trajectory solution after sequential integration through all phases. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +mpf = flow1 * (1.0, flow2) * (2.0, flow3) +sol = mpf((0.0, 3.0), [1.0, 0.0], [0.5, 0.3]) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.Configs.HamiltonianTrajectoryConfig`](@ref). +""" +function (mpf::MultiPhaseHamiltonianFlow)( + tspan::Tuple{Real, Real}, + x0, + p0; + variable=Common.__variable(), + unsafe=Common.__unsafe(), +) + config = Configs.HamiltonianTrajectoryConfig(tspan, x0, p0) + return _evaluate_multiphase(mpf, config; variable=variable, unsafe=unsafe) +end diff --git a/src/MultiPhase/concatenation.jl b/src/MultiPhase/concatenation.jl new file mode 100644 index 00000000..559e29ae --- /dev/null +++ b/src/MultiPhase/concatenation.jl @@ -0,0 +1,193 @@ +# ============================================================================= +# Private helper: switching times validation +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Validate that switching times are strictly increasing. + +Throws a [`PreconditionError`](@extref) if the switching times are not in strictly +increasing order (i.e., if any `switches[i] >= switches[i+1]`). + +# Arguments +- `switches::Vector{<:Real}`: Vector of switching times to validate. + +# Throws +- [`CTBase.Exceptions.PreconditionError`](@extref): If switching times are not strictly increasing. + +# Notes +- This is a private helper function used internally by concatenation operators. +- Strictly increasing means each time must be greater than the previous one. +""" +function _check_switching_times_order(switches::Vector{<:Real}) + for i in 1:(length(switches) - 1) + if switches[i] >= switches[i + 1] + throw(Exceptions.PreconditionError( + "Switching times must be strictly increasing"; + context = "flow concatenation", + reason = "found non-increasing sequence: $switches", + suggestion = "ensure all switching times are in strictly increasing order", + )) + end + end +end + +""" +$(TYPEDSIGNATURES) + +Concatenate two state flows with a switching time using the `*` operator. + +Creates a multi-phase flow with no jump at the switching time. + +# Arguments +- `f1::AbstractStateFlow`: First flow (phase 1). +- `(t_switch, f2)::Tuple{Real, AbstractStateFlow}`: Tuple of switching time and second flow (phase 2). + +# Returns +- `MultiPhaseStateFlow`: Multi-phase flow. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +mpf = flow1 * (1.0, flow2) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function Base.:*(f1::Flows.AbstractStateFlow, (t_switch, f2)::Tuple{Real, Flows.AbstractStateFlow}) + flows = vcat(get_flows(f1), get_flows(f2)) + switches = vcat(get_switching_times(f1), [t_switch], get_switching_times(f2)) + _check_switching_times_order(switches) + jumps = vcat(get_jumps(f1), [nothing], get_jumps(f2)) + return MultiPhaseStateFlow(flows, switches, jumps) +end + +""" +$(TYPEDSIGNATURES) + +Concatenate two state flows with a switching time and jump using the `*` operator. + +Creates a multi-phase flow with a jump function applied at the switching time. + +# Arguments +- `f1::AbstractStateFlow`: First flow (phase 1). +- `(t_switch, jump, f2)::Tuple{Real, Any, AbstractStateFlow}`: Tuple of switching time, jump function, and second flow (phase 2). + +# Returns +- `MultiPhaseStateFlow`: Multi-phase flow with a jump. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +jump = x -> 2 * x +mpf = flow1 * (1.0, jump, flow2) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function Base.:*(f1::Flows.AbstractStateFlow, (t_switch, jump, f2)::Tuple{Real, Any, Flows.AbstractStateFlow}) + flows = vcat(get_flows(f1), get_flows(f2)) + switches = vcat(get_switching_times(f1), [t_switch], get_switching_times(f2)) + _check_switching_times_order(switches) + jumps = vcat(get_jumps(f1), [jump], get_jumps(f2)) + return MultiPhaseStateFlow(flows, switches, jumps) +end + +""" +$(TYPEDSIGNATURES) + +Concatenate two Hamiltonian flows with a switching time using the `*` operator. + +Creates a multi-phase Hamiltonian flow with no jump at the switching time. + +# Arguments +- `f1::AbstractHamiltonianFlow`: First flow (phase 1). +- `(t_switch, f2)::Tuple{Real, AbstractHamiltonianFlow}`: Tuple of switching time and second flow (phase 2). + +# Returns +- `MultiPhaseHamiltonianFlow`: Multi-phase Hamiltonian flow. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +mpf = flow1 * (1.0, flow2) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function Base.:*(f1::Flows.AbstractHamiltonianFlow, (t_switch, f2)::Tuple{Real, Flows.AbstractHamiltonianFlow}) + flows = vcat(get_flows(f1), get_flows(f2)) + switches = vcat(get_switching_times(f1), [t_switch], get_switching_times(f2)) + _check_switching_times_order(switches) + jumps = vcat(get_jumps(f1), [nothing], get_jumps(f2)) + return MultiPhaseHamiltonianFlow(flows, switches, jumps) +end + +""" +$(TYPEDSIGNATURES) + +Concatenate two Hamiltonian flows with a switching time and jump using the `*` operator. + +Creates a multi-phase Hamiltonian flow with a jump function applied at the switching time. + +# Arguments +- `f1::AbstractHamiltonianFlow`: First flow (phase 1). +- `(t_switch, jump, f2)::Tuple{Real, Any, AbstractHamiltonianFlow}`: Tuple of switching time, jump function, and second flow (phase 2). + +# Returns +- `MultiPhaseHamiltonianFlow`: Multi-phase Hamiltonian flow with a jump. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +jump = x -> 2 * x +mpf = flow1 * (1.0, jump, flow2) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function Base.:*(f1::Flows.AbstractHamiltonianFlow, (t_switch, jump, f2)::Tuple{Real, Any, Flows.AbstractHamiltonianFlow}) + flows = vcat(get_flows(f1), get_flows(f2)) + switches = vcat(get_switching_times(f1), [t_switch], get_switching_times(f2)) + _check_switching_times_order(switches) + jumps = vcat(get_jumps(f1), [jump], get_jumps(f2)) + return MultiPhaseHamiltonianFlow(flows, switches, jumps) +end + +""" +$(TYPEDSIGNATURES) + +Concatenate two Hamiltonian flows with a switching time and separate state/costate jumps using the `*` operator. + +Creates a multi-phase Hamiltonian flow with separate jump functions for state and costate applied at the switching time. + +# Arguments +- `f1::AbstractHamiltonianFlow`: First flow (phase 1). +- `(t_switch, jump_x, jump_p, f2)::Tuple{Real, Any, Any, AbstractHamiltonianFlow}`: Tuple of switching time, state jump, costate jump, and second flow (phase 2). + +# Returns +- `MultiPhaseHamiltonianFlow`: Multi-phase Hamiltonian flow with separate jumps. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +jump_x = x -> 2 * x +jump_p = p -> 3 * p +mpf = flow1 * (1.0, jump_x, jump_p, flow2) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function Base.:*(f1::Flows.AbstractHamiltonianFlow, (t_switch, jump_x, jump_p, f2)::Tuple{Real, Any, Any, Flows.AbstractHamiltonianFlow}) + flows = vcat(get_flows(f1), get_flows(f2)) + switches = vcat(get_switching_times(f1), [t_switch], get_switching_times(f2)) + _check_switching_times_order(switches) + jumps = vcat(get_jumps(f1), [(jump_x, jump_p)], get_jumps(f2)) + return MultiPhaseHamiltonianFlow(flows, switches, jumps) +end diff --git a/src/MultiPhase/multiphase_flow.jl b/src/MultiPhase/multiphase_flow.jl new file mode 100644 index 00000000..1df06dad --- /dev/null +++ b/src/MultiPhase/multiphase_flow.jl @@ -0,0 +1,425 @@ +""" +$(TYPEDEF) + +Multi-phase flow for state systems. + +Concatenates multiple state flows with switching times and optional jumps for +sequential integration. Each phase uses its own flow (system + integrator). + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `S <: AbstractStateSystem{TD, VD}`: The state system type +- `I <: AbstractIntegrator`: The integrator type + +# Fields +- `flows::Vector{StateFlow{TD, VD, S, I}}`: Vector of state flows for each phase +- `switching_times::Vector{<:Real}`: Switching times between phases +- `jumps::Vector{<:Any}`: Optional jump functions applied at switching times + +# Example +\`\`\`julia +using CTFlows.MultiPhase, CTFlows.Flows + +flow1 = StateFlow(system1, integrator1) +flow2 = StateFlow(system2, integrator2) +mpf = MultiPhaseStateFlow([flow1, flow2], [1.0], [nothing]) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.Flows.StateFlow`](@ref). +""" +struct MultiPhaseStateFlow{ + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + S<:Systems.AbstractStateSystem{TD, VD}, + I<:Integrators.AbstractIntegrator, + ST<:Vector{<:Real}, + J<:Vector{<:Any}} <: Flows.AbstractStateFlow{TD, VD, S} + flows::Vector{Flows.StateFlow{TD, VD, S, I}} + switching_times::ST + jumps::J +end + +""" +$(TYPEDEF) + +Multi-phase flow for Hamiltonian systems. + +Concatenates multiple Hamiltonian flows with switching times and optional jumps for +sequential integration. Each phase uses its own flow (system + integrator). + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `S <: AbstractHamiltonianSystem{TD, VD}`: The Hamiltonian system type +- `I <: AbstractIntegrator`: The integrator type + +# Fields +- `flows::Vector{HamiltonianFlow{TD, VD, S, I}}`: Vector of Hamiltonian flows for each phase +- `switching_times::Vector{<:Real}`: Switching times between phases +- `jumps::Vector{<:Any}`: Optional jump functions applied at switching times + +# Example +\`\`\`julia +using CTFlows.MultiPhase, CTFlows.Flows + +flow1 = HamiltonianFlow(system1, integrator1) +flow2 = HamiltonianFlow(system2, integrator2) +mpf = MultiPhaseHamiltonianFlow([flow1, flow2], [1.0], [nothing]) +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.Flows.HamiltonianFlow`](@ref). +""" +struct MultiPhaseHamiltonianFlow{ + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + S<:Systems.AbstractHamiltonianSystem{TD, VD, <:Traits.AbstractADTrait}, + I<:Integrators.AbstractIntegrator, + ST<:Vector{<:Real}, + J<:Vector{<:Any}} <: Flows.AbstractHamiltonianFlow{TD, VD, S} + flows::Vector{Flows.HamiltonianFlow{TD, VD, S, I}} + switching_times::ST + jumps::J +end + +const AnyMultiPhaseFlow = Union{MultiPhaseStateFlow, MultiPhaseHamiltonianFlow} + +""" +$(TYPEDSIGNATURES) + +Return the systems associated with a multi-phase state flow. + +# Arguments +- `mpsf::Union{MultiPhaseStateFlow, MultiPhaseHamiltonianFlow}`: The multi-phase state flow. + +# Returns +- `Vector{S}`: Vector of systems for each phase. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +systems = Flows.system(mpf) # Returns vector of systems +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.Flows.system`](@ref). +""" +function Flows.system(mpsf::AnyMultiPhaseFlow) + return [Flows.system(f) for f in mpsf.flows] +end + +""" +$(TYPEDSIGNATURES) + +Return the integrators associated with a multi-phase state flow. + +# Arguments +- `mpsf::Union{MultiPhaseStateFlow, MultiPhaseHamiltonianFlow}`: The multi-phase state flow. + +# Returns +- `Vector{I}`: Vector of integrators for each phase. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +integrators = Flows.integrator(mpf) # Returns vector of integrators +\`\`\` + +See also: [`CTFlows.MultiPhase.MultiPhaseStateFlow`](@ref), [`CTFlows.MultiPhase.MultiPhaseHamiltonianFlow`](@ref), [`CTFlows.Flows.integrator`](@ref). +""" +function Flows.integrator(mpsf::AnyMultiPhaseFlow) + return [Flows.integrator(f) for f in mpsf.flows] +end + +# ============================================================================== +# Getter methods for encapsulation +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Get the number of phases in a multi-phase flow. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. + +# Returns +- `Int`: Number of phases. +""" +function n_phases(mpf::AnyMultiPhaseFlow) + return length(mpf.flows) +end + +""" +$(TYPEDSIGNATURES) + +Get the flow at phase index i. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. +- `i::Int`: Phase index (1-based). + +# Returns +- The flow at phase i (StateFlow or HamiltonianFlow). +""" +function get_flow(mpf::AnyMultiPhaseFlow, i::Int) + return mpf.flows[i] +end + +""" +$(TYPEDSIGNATURES) + +Get the switching time at index i. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. +- `i::Int`: Switching time index (1-based). + +# Returns +- `Real`: The switching time. +""" +function get_switching_time(mpf::AnyMultiPhaseFlow, i::Int) + return mpf.switching_times[i] +end + +""" +$(TYPEDSIGNATURES) + +Get the jump at index i. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. +- `i::Int`: Jump index (1-based). + +# Returns +- The jump value (may be `nothing` if no jump). +""" +function get_jump(mpf::AnyMultiPhaseFlow, i::Int) + return mpf.jumps[i] +end + +# ============================================================================== +# Helper methods for concatenation (abstract flow interface) +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Get the flows from a single-phase flow. + +For single-phase flows (StateFlow, HamiltonianFlow), returns a single-element vector +containing the flow itself. This uniform interface enables concatenation with +multi-phase flows. + +# Arguments +- `f::AbstractFlow`: The single-phase flow. + +# Returns +- `Vector`: A single-element vector containing the flow. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +flow = StateFlow(system, integrator) +flows = get_flows(flow) # Returns [flow] +\`\`\` + +See also: [`CTFlows.MultiPhase.get_flows(::AnyMultiPhaseFlow)`](@ref), [`CTFlows.MultiPhase.get_switching_times`](@ref). +""" +function get_flows(f::Flows.AbstractFlow) + return [f] +end + +""" +$(TYPEDSIGNATURES) + +Get the switching times from a single-phase flow. + +For single-phase flows, returns an empty vector since there are no switching times. + +# Arguments +- `f::AbstractFlow`: The single-phase flow. + +# Returns +- `Vector{Real}`: An empty vector. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +flow = StateFlow(system, integrator) +times = get_switching_times(flow) # Returns Real[] +\`\`\` + +See also: [`CTFlows.MultiPhase.get_switching_times(::AnyMultiPhaseFlow)`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function get_switching_times(f::Flows.AbstractFlow) + return Real[] +end + +""" +$(TYPEDSIGNATURES) + +Get the jumps from a single-phase flow. + +For single-phase flows, returns an empty vector since there are no jumps. + +# Arguments +- `f::AbstractFlow`: The single-phase flow. + +# Returns +- `Vector{Any}`: An empty vector. + +# Example +\`\`\`julia +using CTFlows.Flows, CTFlows.MultiPhase + +flow = StateFlow(system, integrator) +jumps = get_jumps(flow) # Returns Any[] +\`\`\` + +See also: [`CTFlows.MultiPhase.get_jumps(::AnyMultiPhaseFlow)`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function get_jumps(f::Flows.AbstractFlow) + return Any[] +end + +""" +$(TYPEDSIGNATURES) + +Get the flows from a multi-phase flow. + +For multi-phase flows, returns the vector of flows for all phases. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. + +# Returns +- `Vector`: The vector of flows for each phase. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +flows = get_flows(mpf) # Returns mpf.flows +\`\`\` + +See also: [`CTFlows.MultiPhase.get_flows(::AbstractFlow)`](@ref), [`CTFlows.MultiPhase.get_switching_times`](@ref). +""" +function get_flows(mpf::AnyMultiPhaseFlow) + return mpf.flows +end + +""" +$(TYPEDSIGNATURES) + +Get the switching times from a multi-phase flow. + +For multi-phase flows, returns the vector of switching times between phases. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. + +# Returns +- `Vector{<:Real}`: The vector of switching times. + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +times = get_switching_times(mpf) # Returns mpf.switching_times +\`\`\` + +See also: [`CTFlows.MultiPhase.get_switching_times(::AbstractFlow)`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function get_switching_times(mpf::AnyMultiPhaseFlow) + return mpf.switching_times +end + +""" +$(TYPEDSIGNATURES) + +Get the jumps from a multi-phase flow. + +For multi-phase flows, returns the vector of jump functions applied at switching times. + +# Arguments +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow. + +# Returns +- `Vector{<:Any}`: The vector of jump functions (may contain `nothing` for no jump). + +# Example +\`\`\`julia +using CTFlows.MultiPhase + +jumps = get_jumps(mpf) # Returns mpf.jumps +\`\`\` + +See also: [`CTFlows.MultiPhase.get_jumps(::AbstractFlow)`](@ref), [`CTFlows.MultiPhase.get_flows`](@ref). +""" +function get_jumps(mpf::AnyMultiPhaseFlow) + return mpf.jumps +end + +# ============================================================================== +# Base.show +# ============================================================================== + +""" +$(TYPEDSIGNATURES) + +Display a multi-phase flow in REPL format. + +Shows the type name, number of phases, systems, integrators, switching times, and jumps. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow to display. +""" +function Base.show(io::IO, ::MIME"text/plain", mpf::AnyMultiPhaseFlow) + print(io, nameof(typeof(mpf))) + print(io, "\n phases: ", length(mpf.flows)) + + # Show simplified system type with element type + sys = Flows.system(mpf) + if !isempty(sys) + print(io, "\n systems: ", nameof(typeof(first(sys))), "[", length(mpf.flows), "]") + else + print(io, "\n systems: []") + end + + # Show simplified integrator type with element type + integ = Flows.integrator(mpf) + if !isempty(integ) + print(io, "\n integrators: ", nameof(typeof(first(integ))), "[", length(mpf.flows), "]") + else + print(io, "\n integrators: []") + end + + print(io, "\n switching_times: ", mpf.switching_times) + print(io, "\n jumps: ", mpf.jumps) +end + +""" +$(TYPEDSIGNATURES) + +Compact display of a multi-phase flow. + +Shows the type name, number of phases, and switching times. + +# Arguments +- `io::IO`: The IO stream to write to. +- `mpf::AnyMultiPhaseFlow`: The multi-phase flow to display. +""" +function Base.show(io::IO, mpf::AnyMultiPhaseFlow) + print(io, nameof(typeof(mpf)), "(") + parts = String[] + push!(parts, "phases=$(length(mpf.flows))") + push!(parts, "switching_times=$(mpf.switching_times)") + print(io, join(parts, ", ")) + print(io, ")") +end diff --git a/src/Solutions/Solutions.jl b/src/Solutions/Solutions.jl new file mode 100644 index 00000000..e4cf046c --- /dev/null +++ b/src/Solutions/Solutions.jl @@ -0,0 +1,55 @@ +""" + Solutions + +Solution types and solution building for CTFlows. + +This module provides: +- `AbstractIntegrationResult`: Abstraction for raw ODE integration results +- `VectorFieldSolution`: Solution type wrapping integration results +- `build_solution`: Solution building functions for different configuration types +- `final_state`, `times`, `evaluate_at`: Semantic accessors for integration results +- `state`, `time_grid`: Semantic accessors for VectorFieldSolution +- `plot`: Plotting functionality for solutions + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Solutions.build_solution`](@ref), [`CTFlows.Solutions.plot`](@ref). +""" +module Solutions + +# ============================================================================== +# External package imports +# ============================================================================== + +import DocStringExtensions: TYPEDSIGNATURES, TYPEDEF +import CTBase.Exceptions +import RecipesBase: RecipesBase, plot + +# ============================================================================== +# Internal submodule imports +# ============================================================================== + +import ..Common: Common +import ..Configs: Configs +import ..Systems: Systems +import ..Integrators: Integrators +import ..Traits: Traits + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "vector_field_solution.jl")) +include(joinpath(@__DIR__, "hamiltonian_vector_field_solution.jl")) +include(joinpath(@__DIR__, "building.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export state, time_grid +export costate +export VectorFieldSolution +export HamiltonianVectorFieldSolution +export build_solution +export plot + +end # module Solutions diff --git a/src/Solutions/building.jl b/src/Solutions/building.jl new file mode 100644 index 00000000..d1d7b4cc --- /dev/null +++ b/src/Solutions/building.jl @@ -0,0 +1,195 @@ +# ============================================================================= +# Solution from VectorFieldSystem +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Default implementation for scalar point configs โ€” return the final state. + +For scalar configurations (`initial_state <: Number`), unwraps the length-1 vector that was +introduced by scalar-promotion at ODE problem construction time. + +This uses compile-time dispatch on the initial state type to avoid runtime type tests. + +# Arguments +- `::Type{Traits.PointTrait}`: The point mode trait type. +- `::Type{Traits.StateTrait}`: The state content trait type. +- `initial_state::Number`: The scalar initial state. +- `result::Integrators.AbstractIntegrationResult`: The integration result. + +# Returns +- `Number`: The unwrapped scalar final state. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Traits.PointTrait`](@ref), [`CTFlows.Traits.StateTrait`](@ref). +""" +function build_solution( + ::Type{Traits.PointTrait}, + ::Type{Traits.StateTrait}, + config::Configs.AbstractConfig, + result::Integrators.AbstractIntegrationResult, +) + return Common.scalarize(Integrators.final_state(result), Configs.initial_state(config)) +end + +""" +$(TYPEDSIGNATURES) + +Default implementation for trajectory configs โ€” wrap the integration result +in a `VectorFieldSolution` for future extensibility. + +# Arguments +- `::Type{Traits.TrajectoryTrait}`: The trajectory mode trait type. +- `::Type{Traits.StateTrait}`: The state content trait type. +- `initial_state`: The initial state. +- `result::Integrators.AbstractIntegrationResult`: The integration result. + +# Returns +- `VectorFieldSolution`: The wrapped integration result. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Configs.TrajectoryTrait`](@ref), [`CTFlows.Configs.StateTrait`](@ref). +""" +function build_solution( + ::Type{Traits.TrajectoryTrait}, + ::Type{Traits.StateTrait}, + config::Configs.AbstractConfig, + result::Integrators.AbstractIntegrationResult, +) + return VectorFieldSolution(result) +end + +# ============================================================================= +# Internal helpers for Hamiltonian solution splitting +# ============================================================================= + + +""" +$(TYPEDSIGNATURES) + +Split an augmented final state `u` into state `x`, costate `p`, and variable costate `pv` components. + +For Hamiltonian systems, `n_p = n_x` always, so the augmented state is `[x; p; pv]` where +`n_pv = length(u) - 2 * n_x`. + +# Arguments +- `u`: Augmented final state `[x; p; pv]` from integration. +- `n::Int`: The state dimension `n_x = n_p`. + +# Returns +- `Tuple{AbstractVector, AbstractVector, AbstractVector}`: Tuple `(x, p, pv)`. + +# Notes +- Internal helper used by `build_solution` for `AugmentedHamiltonianPointConfig`. +- Assumes `n_p = n_x` invariant for Hamiltonian systems. +""" +function _aug_split_solution(u, x0, pv0) + n = length(x0) + return ( + Common.scalarize(u[1:n], x0), + Common.scalarize(u[n+1:2n], x0), + Common.scalarize(u[2n+1:end], pv0), + ) +end + +# ============================================================================= +# Solution from HamiltonianVectorFieldSystem +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Build a solution for Hamiltonian point configs. + +Returns the final state and costate as a tuple `(xf, pf)`, dispatching on the +type of the initial state to handle scalar, vector, and matrix cases. + +# Arguments +- `::Type{Traits.PointTrait}`: The point mode trait type. +- `::Type{Traits.HamiltonianTrait}`: The Hamiltonian content trait type. +- `initial_state`: The initial state (scalar, vector, or matrix). +- `result::Integrators.AbstractIntegrationResult`: The integration result. + +# Returns +- `Tuple`: The final state and costate. Type depends on `initial_state`: + - `Tuple{Number, Number}` for scalar inputs + - `Tuple{AbstractVector, AbstractVector}` for vector inputs + - `Tuple{AbstractMatrix, AbstractMatrix}` for matrix inputs + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Traits.PointTrait`](@ref), [`CTFlows.Configs.HamiltonianTrait`](@ref). +""" +function build_solution( + ::Type{Traits.PointTrait}, + ::Type{Traits.HamiltonianTrait}, + config::Configs.AbstractConfig, + result::Integrators.AbstractIntegrationResult, + ) + u = Integrators.final_state(result) + x0 = Configs.initial_state(config) + x, p = Solutions._ham_split_solution(u, x0) + return (Common.scalarize(x, x0), Common.scalarize(p, x0)) +end + +""" +$(TYPEDSIGNATURES) + +Build a solution for Hamiltonian trajectory configs. + +Wraps the integration result in a `HamiltonianVectorFieldSolution` for future extensibility. + +# Arguments +- `::Type{Traits.TrajectoryTrait}`: The trajectory mode trait type. +- `::Type{Traits.HamiltonianTrait}`: The Hamiltonian content trait type. +- `initial_state`: The initial state. +- `result::Integrators.AbstractIntegrationResult`: The integration result. + +# Returns +- `HamiltonianVectorFieldSolution`: The wrapped integration result. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.HamiltonianVectorFieldSolution`](@ref), [`CTFlows.Configs.TrajectoryTrait`](@ref), [`CTFlows.Configs.HamiltonianTrait`](@ref). +""" +function build_solution( + ::Type{Traits.TrajectoryTrait}, + ::Type{Traits.HamiltonianTrait}, + config::Configs.AbstractConfig, + result::Integrators.AbstractIntegrationResult, + ) + x0 = Configs.initial_state(config) + return HamiltonianVectorFieldSolution(x0, result) +end + +# ============================================================================= +# Solution from augmented Hamiltonian systems +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Build a solution for augmented Hamiltonian point configs. + +Returns the final state, costate, and variable costate as a tuple `(xf, pf, pvf)`. +For Hamiltonian systems, `n_p = n_x` always, so the augmented state `[x; p; pv]` +splits using only the state dimension `n = length(initial_state)`. + +# Arguments +- `::Type{Traits.PointTrait}`: The point mode trait type. +- `::Type{Traits.AugmentedHamiltonianTrait}`: The augmented Hamiltonian content trait type. +- `initial_state`: The initial state (used to determine state dimension `n`). +- `result::Integrators.AbstractIntegrationResult`: The integration result. + +# Returns +- `Tuple{AbstractVector, AbstractVector, AbstractVector}`: Tuple `(xf, pf, pvf)`. + +# Notes +- Uses `_aug_split_solution` helper to split the augmented final state. +- Assumes `n_p = n_x` invariant for Hamiltonian systems. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Traits.PointTrait`](@ref), [`CTFlows.Configs.AugmentedHamiltonianTrait`](@ref). +""" +function build_solution( + ::Type{Traits.PointTrait}, + ::Type{Traits.AugmentedHamiltonianTrait}, + config::Configs.AbstractConfig, + result::Integrators.AbstractIntegrationResult, +) + return _aug_split_solution(Integrators.final_state(result), Configs.initial_state(config), Configs.initial_variable_costate(config)) +end diff --git a/src/Solutions/hamiltonian_vector_field_solution.jl b/src/Solutions/hamiltonian_vector_field_solution.jl new file mode 100644 index 00000000..2e3bbdd6 --- /dev/null +++ b/src/Solutions/hamiltonian_vector_field_solution.jl @@ -0,0 +1,328 @@ +""" +$(TYPEDEF) + +Abstract supertype for Hamiltonian vector field solution containers. + +This type defines the interface for all solution types that wrap ODE integration +results for Hamiltonian systems. + +See also: [`CTFlows.Solutions.HamiltonianVectorFieldSolution`](@ref), [`CTFlows.Integrators.AbstractIntegrationResult`](@ref). +""" +abstract type AbstractHamiltonianVectorFieldSolution end + +""" +$(TYPEDEF) + +Container for the integration result from a HamiltonianTrajectoryConfig integration. + +This type wraps the integration result returned by integrators and provides +semantic accessors for time grids, state functions, and costate functions. + +# Fields +- `result`: The integration result object (subtype of `AbstractIntegrationResult`). + +# Accessors +- `times(sol)`: Get the time grid (alias: `time_grid(sol)`) +- `state(sol)`: Get the solution as a callable state function `x(t)` +- `costate(sol)`: Get the solution as a callable costate function `p(t)` +- `sol(t)`: Evaluate the solution at time `t`, returning tuple `(x(t), p(t))` + +# Example +```julia +using CTFlows.Solutions + +sol = HamiltonianVectorFieldSolution(result) +ts = times(sol) # or time_grid(sol) +x = state(sol) # callable state function x(t) +p = costate(sol) # callable costate function p(t) +x(0.5), p(0.5) # evaluate at t = 0.5 +x0, p0 = sol(0.0) # returns tuple (x(0), p(0)) +``` + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.AbstractHamiltonianVectorFieldSolution`](@ref). +""" +struct HamiltonianVectorFieldSolution{X0, R<:Integrators.AbstractIntegrationResult} <: AbstractHamiltonianVectorFieldSolution + x0::X0 + result::R +end + +# ============================================================================= +# Internal helper for splitting solutions based on initial state shape +# ============================================================================= + +""" + _ham_split_solution(u::AbstractVector, x0::Number) = (only(u[1:1]), only(u[2:2])) + _ham_split_solution(u::AbstractVector, x0::AbstractVector) = (u[1:n], u[n+1:2n]) + _ham_split_solution(u::AbstractMatrix, x0::AbstractMatrix) = (u[1:n, :], u[n+1:2n, :]) + +Split a combined state vector into state and costate components, preserving the shape of x0. + +For scalar x0, extracts single elements and coerces them back to scalars. +For vector/matrix x0, extracts views of the appropriate size. +""" +_ham_split_solution(u::AbstractVector, x0::Number) = (only(u[1:1]), only(u[2:2])) +_ham_split_solution(u::AbstractVector, x0::AbstractVector) = let n = length(x0) + (u[1:n], u[n+1:2n]) +end +_ham_split_solution(u::AbstractMatrix, x0::AbstractMatrix) = let n = size(x0, 1) + (u[1:n, :], u[n+1:2n, :]) +end + +# ============================================================================= +# Semantic Accessors and Delegation +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return the vector of time points from the solution. + +Delegates to `Integrators.times(sol.result)`. + +# Arguments +- `sol::HamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. + +# Returns +- `AbstractVector`: The vector of time points. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Integrators.evaluate_at`](@ref). +""" +function Integrators.times(sol::HamiltonianVectorFieldSolution) + return Integrators.times(sol.result) +end + +""" +$(TYPEDSIGNATURES) + +Alias for `times(sol)` โ€” returns the time grid from the solution. + +This is an alternative, more explicit name for `times` in numerical contexts +where "time grid" is the standard terminology. + +# Arguments +- `sol::HamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. + +# Returns +- `AbstractVector`: The vector of time points. + +See also: [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref). +""" +function time_grid(sol::HamiltonianVectorFieldSolution) + return Integrators.times(sol) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate the solution at a given time, returning a tuple `(x(t), p(t))`. + +Splits the combined state vector into state and costate halves. + +# Arguments +- `sol::HamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. +- `t::Real`: The time at which to evaluate the solution. + +# Returns +- `Tuple{AbstractVector, AbstractVector}`: The state `x(t)` and costate `p(t)` at time `t`. + +See also: [`CTFlows.Solutions.evaluate_at`](@ref), [`CTFlows.Solutions.times`](@ref). +""" +function (sol::HamiltonianVectorFieldSolution)(t::Real) + u = Integrators.evaluate_at(sol.result, t) + return _ham_split_solution(u, sol.x0) +end + +""" +$(TYPEDSIGNATURES) + +Return the solution as a state function of time `x(t)`. + +This is a semantic accessor that returns a function which, when called with a time, +returns only the state component `x(t)` (not the costate). + +# Arguments +- `sol::HamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. + +# Returns +- `Function`: A function `t -> x(t)` that returns the state at time `t`. + +# Example +```julia +using CTFlows.Solutions + +sol = HamiltonianVectorFieldSolution(result) +x = state(sol) # x is a function of time +x(0.0) # initial state +x(0.5) # interpolated state at t = 0.5 +``` + +See also: [`CTFlows.Solutions.costate`](@ref), [`CTFlows.Solutions.times`](@ref). +""" +function state(sol::HamiltonianVectorFieldSolution) + return t -> sol(t)[1] +end + +""" +$(TYPEDSIGNATURES) + +Return the solution as a costate function of time `p(t)`. + +This is a semantic accessor that returns a function which, when called with a time, +returns only the costate component `p(t)` (not the state). + +# Arguments +- `sol::HamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. + +# Returns +- `Function`: A function `t -> p(t)` that returns the costate at time `t`. + +# Example +```julia +using CTFlows.Solutions + +sol = HamiltonianVectorFieldSolution(result) +p = costate(sol) # p is a function of time +p(0.0) # initial costate +p(0.5) # interpolated costate at t = 0.5 +``` + +See also: [`CTFlows.Solutions.state`](@ref), [`CTFlows.Solutions.times`](@ref). +""" +function costate(sol::HamiltonianVectorFieldSolution) + return t -> sol(t)[2] +end + +""" +$(TYPEDSIGNATURES) + +Return the final state and costate from the solution as a tuple `(xf, pf)`. + +Splits the combined state vector into state and costate halves. + +# Arguments +- `sol::HamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. + +# Returns +- `Tuple{AbstractVector, AbstractVector}`: The final state `xf` and final costate `pf`. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.final_state`](@ref). +""" +function Integrators.final_state(sol::HamiltonianVectorFieldSolution) + u = Integrators.final_state(sol.result) + return _ham_split_solution(u, sol.x0) +end + +""" +$(TYPEDSIGNATURES) + +Merge a sequence of HamiltonianVectorFieldSolution objects into a single HamiltonianVectorFieldSolution. + +This extracts the internal integration results, merges them, and wraps the result +in a new HamiltonianVectorFieldSolution. + +# Arguments +- `segments::AbstractVector{<:HamiltonianVectorFieldSolution}`: Sequence of Hamiltonian vector field solutions to merge. + +# Returns +- `HamiltonianVectorFieldSolution`: A merged Hamiltonian vector field solution containing the merged integration result. + +See also: [`CTFlows.Integrators.merge`](@ref), [`CTFlows.Integrators.AbstractIntegrationResult`](@ref). +""" +function Integrators.merge(segments::AbstractVector{<:HamiltonianVectorFieldSolution}) + if isempty(segments) + throw(Exceptions.IncorrectArgument( + "Cannot merge empty sequence of HamiltonianVectorFieldSolution"; + got = "0 segments", + expected = "at least 1 segment", + context = "HamiltonianVectorFieldSolution merge", + )) + end + + internal_results = [sol.result for sol in segments] + merged_result = Integrators.merge(internal_results) + return HamiltonianVectorFieldSolution(segments[1].x0, merged_result) +end + +# ============================================================================= +# Stub methods โ€” to be extended by CTFlowsPlots +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Plot stub โ€” throws error if Plots extension not loaded. + +# Arguments +- `sol::AbstractHamiltonianVectorFieldSolution`: The Hamiltonian vector field solution. +- `kwargs...`: Additional plotting keyword arguments (ignored). + +# Throws +- `CTBase.Exceptions.ExtensionError`: If Plots extension is not loaded. + +See also: [`CTFlows.Solutions.HamiltonianVectorFieldSolution`](@ref), [`CTFlows.Solutions.AbstractHamiltonianVectorFieldSolution`](@ref). +""" +function RecipesBase.plot(sol::AbstractHamiltonianVectorFieldSolution; kwargs...) + throw( + Exceptions.ExtensionError( + :Plots; + message = "to plot solutions", + feature = "Plotting via Plots.jl", + context = "Load Plots extension first: using Plots", + ), + ) +end + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display the `HamiltonianVectorFieldSolution` in a readable text/plain format. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type. +- `sol::HamiltonianVectorFieldSolution`: The solution to display. +""" +function Base.show(io::IO, ::MIME"text/plain", sol::HamiltonianVectorFieldSolution) + print(io, "HamiltonianVectorFieldSolution") + print(io, "\n result: ", nameof(typeof(sol.result))) + + try + ts = times(sol) + if !isempty(ts) + print(io, "\n time span: (", first(ts), ", ", last(ts), ")") + print(io, "\n time points: ", length(ts)) + end + catch + end +end + +""" +$(TYPEDSIGNATURES) + +Display the `HamiltonianVectorFieldSolution` in a compact one-line format. + +# Arguments +- `io::IO`: The IO stream to write to. +- `sol::HamiltonianVectorFieldSolution`: The solution to display. +""" +function Base.show(io::IO, sol::HamiltonianVectorFieldSolution) + print(io, "HamiltonianVectorFieldSolution(") + parts = String[] + push!(parts, "result=$(nameof(typeof(sol.result)))") + + try + ts = times(sol) + if !isempty(ts) + push!(parts, "tspan=($(first(ts)), $(last(ts)))") + push!(parts, "n=$(length(ts))") + end + catch + end + + print(io, join(parts, ", ")) + print(io, ")") +end diff --git a/src/Solutions/vector_field_solution.jl b/src/Solutions/vector_field_solution.jl new file mode 100644 index 00000000..c6b957e5 --- /dev/null +++ b/src/Solutions/vector_field_solution.jl @@ -0,0 +1,289 @@ +""" +$(TYPEDEF) + +Abstract supertype for vector field solution containers. + +This type defines the interface for all solution types that wrap ODE integration results. + +See also: [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Integrators.AbstractIntegrationResult`](@ref). +""" +abstract type AbstractVectorFieldSolution end + +""" +$(TYPEDEF) + +Container for the integration result from a StateTrajectoryConfig integration. + +This type wraps the integration result returned by integrators and provides +semantic accessors for time grids and state functions. + +# Fields +- `result`: The integration result object (subtype of `AbstractIntegrationResult`). + +# Accessors +- `times(sol)`: Get the time grid (alias: `time_grid(sol)`) +- `state(sol)`: Get the solution as a callable state function +- `sol(t)`: Evaluate the solution at time `t` (equivalent to `state(sol)(t)`) + +# Example +\`\`\`julia +using CTFlows.Solutions + +sol = VectorFieldSolution(result) +ts = times(sol) # or time_grid(sol) +x = state(sol) # callable state function +x(0.5) # evaluate at t = 0.5 +\`\`\` + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.AbstractVectorFieldSolution`](@ref). +""" +struct VectorFieldSolution{R<:Integrators.AbstractIntegrationResult} <: AbstractVectorFieldSolution + result::R +end + +# ============================================================================= +# Semantic Accessors and Delegation +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return the vector of time points from the solution. + +Delegates to `Integrators.times(sol.result)`. + +# Arguments +- `sol::VectorFieldSolution`: The vector field solution. + +# Returns +- `AbstractVector`: The vector of time points. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Integrators.evaluate_at`](@ref). +""" +function Integrators.times(sol::VectorFieldSolution) + return Integrators.times(sol.result) +end + +""" +$(TYPEDSIGNATURES) + +Return the solution itself as a state function of time. + +This is a semantic accessor that returns `sol` itself (which is already callable), +providing a clear, self-documenting way to obtain the trajectory function. + +# Arguments +- `sol::VectorFieldSolution`: The vector field solution. + +# Returns +- `VectorFieldSolution`: The solution itself, which is callable as a function of time. + +# Example +\`\`\`julia +using CTFlows.Solutions + +sol = VectorFieldSolution(result) +x = state(sol) # x is a function of time +x(0.0) # initial state +x(0.5) # interpolated state at t = 0.5 +x.(0.0:0.1:1.0) # broadcast over time grid +\`\`\` + +# Notes +- This accessor provides a foundation for uniform semantic accessors in optimal control: + `state(sol)`, `costate(sol)`, `control(sol)` when extended to Hamiltonian systems. +- No allocation occurs โ€” returns `sol` directly. + +See also: [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.evaluate_at`](@ref), [`CTFlows.Solutions.time_grid`](@ref). +""" +function state(sol::VectorFieldSolution) + return sol +end + +""" +$(TYPEDSIGNATURES) + +Alias for `times(sol)` โ€” returns the time grid from the solution. + +This is an alternative, more explicit name for `times` in numerical contexts +where "time grid" is the standard terminology. + +# Arguments +- `sol::VectorFieldSolution`: The vector field solution. + +# Returns +- `AbstractVector`: The vector of time points. + +# Example +\`\`\`julia +using CTFlows.Solutions + +sol = VectorFieldSolution(result) +tg = time_grid(sol) # same as times(sol) +\`\`\` + +# Notes +- `time_grid` and `times` are two names for the same operation. +- Use `time_grid` when "grid" terminology is clearer in context. +- Use `times` for brevity in everyday use. + +See also: [`CTFlows.Solutions.times`](@ref), [`CTFlows.Solutions.state`](@ref). +""" +function time_grid(sol::VectorFieldSolution) + return Integrators.times(sol) +end + +""" +$(TYPEDSIGNATURES) + +Evaluate the solution at a given time by delegating to the integration result. + +# Arguments +- `sol::VectorFieldSolution`: The vector field solution. +- `t::Real`: The time at which to evaluate the solution. + +# Returns +- The solution state at time `t`. + +See also: [`CTFlows.Solutions.evaluate_at`](@ref), [`CTFlows.Solutions.times`](@ref). +""" +function (sol::VectorFieldSolution)(t::Real) + return Integrators.evaluate_at(sol.result, t) +end + +""" +$(TYPEDSIGNATURES) + +Return the final state from the solution by delegating to the integration result. + +# Arguments +- `sol::VectorFieldSolution`: The vector field solution. + +# Returns +- The final state from the integration result. + +See also: [`CTFlows.Integrators.AbstractIntegrationResult`](@ref), [`CTFlows.Solutions.final_state`](@ref). +""" +function Integrators.final_state(sol::VectorFieldSolution) + return Integrators.final_state(sol.result) +end + +""" +$(TYPEDSIGNATURES) + +Merge a sequence of VectorFieldSolution objects into a single VectorFieldSolution. + +This extracts the internal integration results, merges them, and wraps the result +in a new VectorFieldSolution. + +# Arguments +- `segments::AbstractVector{<:VectorFieldSolution}`: Sequence of vector field solutions to merge. + +# Returns +- `VectorFieldSolution`: A merged vector field solution containing the merged integration result. + +See also: [`CTFlows.Integrators.merge`](@ref), [`CTFlows.Integrators.AbstractIntegrationResult`](@ref). +""" +function Integrators.merge(segments::AbstractVector{<:VectorFieldSolution}) + if isempty(segments) + throw(Exceptions.IncorrectArgument( + "Cannot merge empty sequence of VectorFieldSolution"; + got = "0 segments", + expected = "at least 1 segment", + context = "VectorFieldSolution merge", + )) + end + + # Extract internal results + internal_results = [sol.result for sol in segments] + + # Merge the internal results + merged_result = Integrators.merge(internal_results) + + # Wrap in VectorFieldSolution + return VectorFieldSolution(merged_result) +end + +# ============================================================================= +# Stub methods โ€” to be extended by CTFlowsPlots +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Plot stub โ€” throws error if Plots extension not loaded. + +# Arguments +- `sol::AbstractVectorFieldSolution`: The vector field solution. +- `kwargs...`: Additional plotting keyword arguments (ignored). + +# Throws +- `CTBase.Exceptions.ExtensionError`: If Plots extension is not loaded. + +See also: [`CTFlows.Solutions.VectorFieldSolution`](@ref), [`CTFlows.Solutions.AbstractVectorFieldSolution`](@ref). +""" +function RecipesBase.plot(sol::AbstractVectorFieldSolution; kwargs...) + throw( + Exceptions.ExtensionError( + :Plots; + message = "to plot solutions", + feature = "Plotting via Plots.jl", + context = "Load Plots extension first: using Plots", + ), + ) +end + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display the `VectorFieldSolution` in a readable text/plain format. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type. +- `sol::VectorFieldSolution`: The solution to display. +""" +function Base.show(io::IO, ::MIME"text/plain", sol::VectorFieldSolution) + print(io, "VectorFieldSolution") + print(io, "\n result: ", nameof(typeof(sol.result))) + + try + ts = times(sol) + if !isempty(ts) + print(io, "\n time span: (", first(ts), ", ", last(ts), ")") + print(io, "\n time points: ", length(ts)) + end + catch + end +end + +""" +$(TYPEDSIGNATURES) + +Display the `VectorFieldSolution` in a compact one-line format. + +# Arguments +- `io::IO`: The IO stream to write to. +- `sol::VectorFieldSolution`: The solution to display. +""" +function Base.show(io::IO, sol::VectorFieldSolution) + print(io, "VectorFieldSolution(") + parts = String[] + push!(parts, "result=$(nameof(typeof(sol.result)))") + + try + ts = times(sol) + if !isempty(ts) + push!(parts, "tspan=($(first(ts)), $(last(ts)))") + push!(parts, "n=$(length(ts))") + end + catch + end + + print(io, join(parts, ", ")) + print(io, ")") +end diff --git a/src/Systems/Systems.jl b/src/Systems/Systems.jl new file mode 100644 index 00000000..7b7c931d --- /dev/null +++ b/src/Systems/Systems.jl @@ -0,0 +1,49 @@ +""" + Systems + +System types and contracts for CTFlows. + +This module defines the `AbstractSystem` type and its required methods: +- `rhs`: returns the right-hand side function for integration +- `dimensions`: returns dimensional information (state, costate, control, variable) +""" +module Systems + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions + +# ============================================================================== +# Internal sibling-submodule imports +# ============================================================================== + +import ..Common: Common +import ..Traits: Traits +import ..Data: Data +import ..Differentiation: Differentiation + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "abstract_system.jl")) +include(joinpath(@__DIR__, "vector_field_system.jl")) +include(joinpath(@__DIR__, "hamiltonian_vector_field_system.jl")) +include(joinpath(@__DIR__, "hamiltonian_system.jl")) +include(joinpath(@__DIR__, "building.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractSystem, AbstractStateSystem, AbstractHamiltonianSystem +export rhs +export build_rhs +export build_oop_rhs +export VectorFieldSystem +export HamiltonianVectorFieldSystem +export HamiltonianSystem +export build_system +export build_rhs_augmented + +end # module Systems diff --git a/src/Systems/abstract_system.jl b/src/Systems/abstract_system.jl new file mode 100644 index 00000000..87ecf504 --- /dev/null +++ b/src/Systems/abstract_system.jl @@ -0,0 +1,335 @@ +""" +$(TYPEDEF) + +Abstract type for all systems in CTFlows. + +An `AbstractSystem` represents a fully assembled object that can be integrated. +It embeds its own `rhs`, dimensional metadata, and solution-building logic. + +# Contract + +All subtypes must implement: + +- `rhs(system::AbstractSystem)`: Returns a function `(du, u, p, t) -> nothing` that fills `du` in place. + +# Example + +\`\`\`julia +using CTFlows.Systems +using CTFlows.Common + +# Define a concrete system +struct MySystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +# Implement required contract method +function Systems.rhs(sys::MySystem) + return (du, u, p, t) -> du .= sys.data .* u +end +\`\`\` + +See also: [`CTFlows.Systems.rhs`](@ref), [`CTFlows.Traits.time_dependence`](@ref), [`CTFlows.Traits.variable_dependence`](@ref). +""" +abstract type AbstractSystem{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence} end + +""" +$(TYPEDEF) + +Abstract type for state systems (non-Hamiltonian). + +Subtype of `AbstractSystem` specialized for state dynamics without costates. +Carries the time-dependence and variable-dependence traits for compile-time dispatch. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) + +# Example +\`\`\`julia-repl +julia> using CTFlows.Systems + +julia> MyStateSystem <: Systems.AbstractStateSystem +true +\`\`\` + +See also: [`CTFlows.Systems.AbstractSystem`](@ref), [`CTFlows.Systems.AbstractHamiltonianSystem`](@ref). +""" +abstract type AbstractStateSystem{TD, VD} <: AbstractSystem{TD, VD} end + +""" +$(TYPEDEF) + +Abstract type for Hamiltonian systems. + +Subtype of `AbstractSystem` specialized for Hamiltonian dynamics with state and costate. +Carries the time-dependence and variable-dependence traits for compile-time dispatch. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `AT <: AbstractADTrait`: AD capability trait (WithAD or WithoutAD) + +# Example +\`\`\`julia-repl +julia> using CTFlows.Systems + +julia> MyHamiltonianSystem <: Systems.AbstractHamiltonianSystem +true +\`\`\` + +See also: [`CTFlows.Systems.AbstractSystem`](@ref), [`CTFlows.Systems.AbstractStateSystem`](@ref). +""" +abstract type AbstractHamiltonianSystem{TD, VD, AT<:Traits.AbstractADTrait} <: AbstractSystem{TD, VD} end + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractSystem` has the time-dependence trait. + +This implementation declares that all systems support time-dependence queries. +Concrete subtypes must implement `time_dependence` to return the specific trait value. + +# Example + +\`\`\`julia +using CTFlows.Systems +using CTFlows.Common + +struct MySystem <: Systems.AbstractSystem end + +# All systems have the time-dependence trait +Traits.has_time_dependence_trait(MySystem) # Returns true + +# Concrete subtypes must implement time_dependence +function Traits.time_dependence(sys::MySystem) + return Traits.Autonomous +end +\`\`\` + +See also: [`CTFlows.Traits.time_dependence`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref). +""" +Traits.has_time_dependence_trait(::AbstractSystem) = true + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractSystem` has the variable-dependence trait. + +This implementation declares that all systems support variable-dependence queries. +Concrete subtypes must implement `variable_dependence` to return the specific trait value. + +# Example + +\`\`\`julia +using CTFlows.Systems +using CTFlows.Common + +struct MySystem <: Systems.AbstractSystem end + +# All systems have the variable-dependence trait +Traits.has_variable_dependence_trait(MySystem) # Returns true + +# Concrete subtypes must implement variable_dependence +function Traits.variable_dependence(sys::MySystem) + return Traits.NonFixed +end +\`\`\` + +See also: [`CTFlows.Traits.variable_dependence`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref). +""" +Traits.has_variable_dependence_trait(::AbstractSystem) = true + +""" +$(TYPEDSIGNATURES) + +Extract the time dependence trait from an `AbstractSystem`. + +# Returns +- `Type{<:TimeDependence}`: The time dependence trait type (Autonomous or NonAutonomous). + +# Example +\`\`\`julia +using CTFlows.Systems +using CTFlows.Common + +struct MySystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +Traits.time_dependence(MySystem) # Returns Autonomous +\`\`\` + +See also: [`CTFlows.Traits.has_time_dependence_trait`](@ref), [`CTFlows.Traits.is_autonomous`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref). +""" +function Traits.time_dependence(sys::AbstractSystem{TD, <:Traits.VariableDependence}) where {TD <: Traits.TimeDependence} + return TD +end + +""" +$(TYPEDSIGNATURES) + +Extract the variable dependence trait from an `AbstractSystem`. + +# Returns +- `Type{<:VariableDependence}`: The variable dependence trait type (Fixed or NonFixed). + +# Example +\`\`\`julia +using CTFlows.Systems +using CTFlows.Common + +struct MySystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +Traits.variable_dependence(MySystem) # Returns Fixed +\`\`\` + +See also: [`CTFlows.Traits.has_variable_dependence_trait`](@ref), [`CTFlows.Traits.is_variable`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref). +""" +function Traits.variable_dependence(sys::AbstractSystem{<:Traits.TimeDependence, VD}) where {VD <: Traits.VariableDependence} + return VD +end + +""" +$(TYPEDSIGNATURES) + +Return the automatic differentiation capability trait of a Hamiltonian system. + +# Arguments +- `sys::AbstractHamiltonianSystem`: The Hamiltonian system. + +# Returns +- `AT <: AbstractADTrait`: The AD capability trait, either [`CTFlows.Traits.WithAD`](@ref) or [`CTFlows.Traits.WithoutAD`](@ref). + +# Notes +- [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref) always returns `WithoutAD` since it uses an explicitly provided vector field. +- [`CTFlows.Systems.HamiltonianSystem`](@ref) returns `WithAD` since it uses automatic differentiation to compute gradients from a scalar Hamiltonian function. +- This trait is used for dispatch in flow integration and cache preparation. + +See also: [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Traits.WithAD`](@ref), [`CTFlows.Traits.WithoutAD`](@ref), [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref), [`CTFlows.Systems.HamiltonianSystem`](@ref). +""" +function Traits.ad_trait(::AbstractHamiltonianSystem{TD, VD, AT}) where {TD, VD, AT} + return AT +end + +""" +$(TYPEDSIGNATURES) + +Return the variable costate capability trait of a system. + +# Arguments +- `sys::AbstractSystem`: The system (default implementation returns `NoVariableCostate`). + +# Returns +- `Type{<:AbstractVariableCostateCapability}`: The capability trait, either + `SupportsVariableCostate` or `NoVariableCostate`. + +# Notes +- Default implementation returns `NoVariableCostate` for all systems +- Specialized implementation on `HamiltonianSystem` with `NonFixed` returns `SupportsVariableCostate` +- This trait is used for dispatch in `call_variable_costate` to determine if augmented integration is possible + +See also: [`CTFlows.Traits.AbstractVariableCostateCapability`](@ref), [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +Traits.variable_costate_trait(::AbstractSystem) = Traits.NoVariableCostate + +""" +$(TYPEDSIGNATURES) + +Return the right-hand side function for the system. + +The returned function must have the signature `(du, u, p, t) -> nothing` and +fill `du` in place with the derivative at state `u`, parameters `p`, and time `t`. + +# Example + +\`\`\`julia +using CTFlows.Systems + +struct MySystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +# Implement rhs to return the ODE right-hand side function +function Systems.rhs(sys::MySystem) + return (du, u, p, t) -> du .= sys.data .* u +end + +# Usage +sys = MySystem([1.0, 2.0]) +rhs_func = Systems.rhs(sys) +du = zeros(2) +rhs_func(du, [3.0, 4.0], [], 0.0) # du becomes [3.0, 8.0] +\`\`\` + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): If not implemented by the concrete type. + +See also: [`CTFlows.Systems.AbstractSystem`](@ref). +""" +function rhs(system::AbstractSystem) + throw( + Exceptions.NotImplemented( + "AbstractSystem rhs method not implemented"; + required_method = "rhs(system::$(typeof(system)))", + suggestion = "Return a function (du, u, p, t) -> nothing that fills du in place.", + context = "AbstractSystem.rhs - required method implementation", + ), + ) +end + +""" +$(TYPEDSIGNATURES) + +Return the out-of-place right-hand side function for the system. + +The returned function must have the signature `(u, p, t) -> du` and +return the derivative at state `u`, parameters `p`, and time `t` without modifying `u`. + +This is used for immutable array types like `StaticArrays.SVector` where in-place +operations are not possible. + +# Example + +```julia +using CTFlows.Systems + +struct MySystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +# Implement rhs_oop to return the ODE right-hand side function (out-of-place) +function Systems.rhs_oop(sys::MySystem) + return (u, p, t) -> sys.data .* u +end + +# Usage +sys = MySystem([1.0, 2.0]) +rhs_oop_func = Systems.rhs_oop(sys) +u = [3.0, 4.0] +p = nothing +t = 0.0 +du = rhs_oop_func(u, p, t) # du = [3.0, 8.0] +``` + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): If not implemented by the concrete type. + +# Notes +- This method is called when `ismutable(u0)` returns `false` for the initial condition. +- For mutable arrays like `Vector`, the in-place `rhs` method is used instead. + +See also: [`CTFlows.Systems.rhs`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref). +""" +function rhs_oop(system::AbstractSystem, ::Bool = true) + throw( + Exceptions.NotImplemented( + "AbstractSystem rhs_oop method not implemented"; + required_method = "rhs_oop(sys::$(typeof(system)))", + suggestion = "Implement rhs_oop for your system type to support immutable array initial conditions.", + context = "AbstractSystem.rhs_oop - required method implementation", + ), + ) +end diff --git a/src/Systems/building.jl b/src/Systems/building.jl new file mode 100644 index 00000000..b33891fa --- /dev/null +++ b/src/Systems/building.jl @@ -0,0 +1,115 @@ +""" +$(TYPEDSIGNATURES) + +Build a `VectorFieldSystem` from a `VectorField`. + +Constructs a concrete system that wraps the vector field and pre-computes its +right-hand side function for integration. The resulting system is ready for use +with flow integration pipelines. + +# Arguments +- `vf::Data.VectorField`: The vector field to wrap into a system. + +# Returns +- `VectorFieldSystem`: A concrete system wrapping the vector field with a pre-computed RHS function. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Systems, CTFlows.Common + +julia> vf = VectorField(x -> -x; autonomous=true, variable=false) +VectorField + time_dependence: Autonomous + variable_dependence: Fixed + function: var"#1" + +julia> sys = build_system(vf) +VectorFieldSystem + time_dependence: Autonomous + variable_dependence: Fixed + vector_field: VectorField{var"#1", Autonomous, Fixed} +\`\`\` + +See also: [`CTFlows.Data.VectorField`](@ref), [`CTFlows.Systems.VectorFieldSystem`](@ref). +""" +function build_system(vf::Data.VectorField) + return VectorFieldSystem(vf) +end + +""" +$(TYPEDSIGNATURES) + +Build a `HamiltonianVectorFieldSystem` from a `HamiltonianVectorField`. + +Constructs a concrete Hamiltonian system that wraps the Hamiltonian vector field. +RHS closures are built lazily based on actual initial condition types during flow integration. + +# Arguments +- `hvf::Data.HamiltonianVectorField`: The Hamiltonian vector field to wrap into a system. + +# Returns +- `HamiltonianVectorFieldSystem`: A concrete Hamiltonian system. + +# Example +```julia-repl +julia> using CTFlows.Systems, CTFlows.Common + +julia> hvf = HamiltonianVectorField((x, p) -> (x, -p); autonomous=true, variable=false) +HamiltonianVectorField + time_dependence: Autonomous + variable_dependence: Fixed + function: var"#1" + +julia> sys = build_system(hvf) +HamiltonianVectorFieldSystem + time_dependence: Autonomous + variable_dependence: Fixed + hamiltonian_vector_field: HamiltonianVectorField{var"#1", Autonomous, Fixed} +``` + +See also: [`CTFlows.Data.HamiltonianVectorField`](@ref), [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref). +""" +function build_system(hvf::Data.HamiltonianVectorField) + return HamiltonianVectorFieldSystem(hvf) +end + +""" +$(TYPEDSIGNATURES) + +Build a [`CTFlows.Systems.HamiltonianSystem`](@ref) from a scalar `Hamiltonian` function with automatic differentiation. + +Constructs a concrete Hamiltonian system that wraps the scalar Hamiltonian function with an AD backend. +RHS closures are built lazily based on actual initial condition types during flow integration. + +# Arguments +- `h::Data.Hamiltonian`: The scalar Hamiltonian function to wrap into a system. +- `backend::Differentiation.AbstractADBackend`: The automatic differentiation backend (e.g., `AutoForwardDiff`, `AutoZygote`). + +# Returns +- `HamiltonianSystem`: A concrete Hamiltonian system with automatic differentiation support. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Systems, CTFlows.Common, CTFlows.Data + +julia> h = Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); autonomous=true, variable=false) +Hamiltonian{var"#1", Autonomous, Fixed} + +julia> sys = build_system(h, AutoForwardDiff()) +HamiltonianSystem + time_dependence: Autonomous + variable_dependence: Fixed + hamiltonian: Hamiltonian{var"#1", Autonomous, Fixed} + backend: AutoForwardDiff() +\`\`\` + +# Notes +- The AD backend is used to compute Hamiltonian gradients `โˆ‚H/โˆ‚x` and `โˆ‚H/โˆ‚p` automatically during integration. +- This overload is for scalar Hamiltonian functions where gradients are computed via AD. For explicit vector fields, use [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref) instead. + +See also: [`CTFlows.Data.Hamiltonian`](@ref), [`CTFlows.Systems.HamiltonianSystem`](@ref), [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref), [`CTFlows.Differentiation.AbstractADBackend`](@ref). +""" +function build_system(h::Data.Hamiltonian, backend::Differentiation.AbstractADBackend) + return HamiltonianSystem(h, backend) +end + diff --git a/src/Systems/hamiltonian_system.jl b/src/Systems/hamiltonian_system.jl new file mode 100644 index 00000000..eb6b4e9e --- /dev/null +++ b/src/Systems/hamiltonian_system.jl @@ -0,0 +1,221 @@ +# ============================================================================= +# HamiltonianSystem โ€” AD-based Hamiltonian system with scalar Hamiltonian function +# ============================================================================= + +""" +$(TYPEDEF) + +Concrete `AbstractHamiltonianSystem` wrapping a scalar `Hamiltonian` function with an AD backend. + +The system does not store pre-computed RHS closures. Instead, closures are built +lazily by `build_rhs` and `build_oop_rhs` based on the actual initial condition +types, ensuring correct handling of scalar, vector (including length-1), and matrix +inputs with consistent output shapes. + +# Type Parameters +- `F`: concrete type of the wrapped Hamiltonian function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. +- `BACKEND <: AbstractADBackend`: concrete AD backend type. + +# Fields +- `h::Hamiltonian{F, TD, VD}`: the underlying scalar Hamiltonian function. +- `backend::BACKEND`: the AD backend for gradient computation. + +# Example +```julia-repl +julia> using CTFlows.Systems, CTFlows.Common, CTFlows.Data + +julia> h = Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); autonomous=true, variable=false) +Hamiltonian{var"#1", Autonomous, Fixed} + +julia> sys = HamiltonianSystem(h, AutoForwardDiff()) +HamiltonianSystem + time_dependence: Autonomous + variable_dependence: Fixed + hamiltonian: Hamiltonian{var"#1", Autonomous, Fixed} + backend: AutoForwardDiff() +``` + +See also: [`CTFlows.Data.Hamiltonian`](@ref), [`CTFlows.Systems.AbstractHamiltonianSystem`](@ref), [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Systems.build_rhs`](@ref), [`CTFlows.Systems.build_oop_rhs`](@ref). +""" +struct HamiltonianSystem{ + F<:Function, + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + BACKEND<:Differentiation.AbstractADBackend, +} <: AbstractHamiltonianSystem{TD, VD, Traits.WithAD} + h::Data.Hamiltonian{F, TD, VD} + backend::BACKEND +end + +function hamiltonian(sys::HamiltonianSystem) + return sys.h +end + +function backend(sys::HamiltonianSystem) + return sys.backend +end + +# ============================================================================= +# Constructors +# ============================================================================= + +function HamiltonianSystem(h::Data.Hamiltonian{F,TD,VD}, backend::Differentiation.AbstractADBackend) where {F,TD,VD} + return HamiltonianSystem{F, TD, VD, typeof(backend)}(h, backend) +end + +# ============================================================================= +# Internal helpers for augmented split/assign (Vector + Matrix, concrete integers) +# ============================================================================= + +function _aug_split(u::AbstractVector, n_x::Int, n_v::Int) + x = n_x == 1 ? u[1] : @view(u[1:n_x]) + p = n_x == 1 ? u[n_x+1] : @view(u[n_x+1:2*n_x]) + pv = @view(u[end-n_v+1:end]) + return (x, p, pv) +end +_aug_split(u::AbstractMatrix, n_x::Int, n_v::Int) = + (@view(u[1:n_x,:]), @view(u[n_x+1:2*n_x,:]), @view(u[end-n_v+1:end,:])) + +_aug_assign!(du::AbstractVector, dx, dp, dpv, n_x::Int, n_v::Int) = + (du[1:n_x] .= dx; du[n_x+1:2*n_x] .= dp; du[end-n_v+1:end] .= dpv) +_aug_assign!(du::AbstractMatrix, dx, dp, dpv, n_x::Int, n_v::Int) = + (du[1:n_x,:] .= dx; du[n_x+1:2*n_x,:] .= dp; du[end-n_v+1:end,:] .= dpv) + +# ============================================================================= +# Public lazy RHS builders +# ============================================================================= + +""" + build_rhs(sys::HamiltonianSystem, x0, p0) -> f!(du, u, ฮป, t) + +Build an in-place RHS closure for the given initial conditions. + +The closure is constructed lazily based on the shapes of `x0` and `p0`, +ensuring correct handling of scalar, vector, and matrix inputs. + +# Arguments +- `sys::HamiltonianSystem`: The Hamiltonian system. +- `x0`: Initial state (scalar, vector, or matrix). +- `p0`: Initial costate (same shape as `x0`). + +# Returns +- `Function`: A closure with signature `(du, u, ฮป, t) -> nothing`. +""" +function build_rhs(sys::HamiltonianSystem, x0, p0) + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + h, backend = sys.h, sys.backend + return function (du, u, ฮป, t) + x, p = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, t, cx(x), cp(p), Common.variable(ฮป), Common.cache(ฮป)) + _ham_assign!(du, โˆ‚p, -โˆ‚x, N) + return nothing + end +end + +""" + build_oop_rhs(sys::HamiltonianSystem, x0, p0) -> f(u, ฮป, t) + +Build an out-of-place RHS closure for the given initial conditions. + +The closure is constructed lazily based on the shapes of `x0` and `p0`, +ensuring correct handling of scalar, vector, and matrix inputs. + +# Arguments +- `sys::HamiltonianSystem`: The Hamiltonian system. +- `x0`: Initial state (scalar, vector, or matrix). +- `p0`: Initial costate (same shape as `x0`). + +# Returns +- `Function`: A closure with signature `(u, ฮป, t) -> du`. +""" +function build_oop_rhs(sys::HamiltonianSystem, x0, p0) + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + h, backend = sys.h, sys.backend + return function (u, ฮป, t) + x, p = _ham_split(u, N) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, t, cx(x), cp(p), Common.variable(ฮป), Common.cache(ฮป)) + return vcat(โˆ‚p, -โˆ‚x) + end +end + +# ============================================================================= +# Batch compatibility check for augmented RHS +# ============================================================================= + +function _check_aug_batch_compat(u::AbstractMatrix, v::AbstractMatrix) + if size(u, 2) != size(v, 2) + throw(Exceptions.PreconditionError( + "batch size mismatch in augmented Hamiltonian RHS"; + reason = "size(u, 2) = $(size(u, 2)) โ‰  size(v, 2) = $(size(v, 2))", + context = "build_rhs_augmented โ€” matrix batch mode", + suggestion = "variable v must have the same number of columns as the state u", + )) + end + return nothing +end +_check_aug_batch_compat(u, v) = nothing # no-op for non-matrix cases + +# ============================================================================= +# build_rhs_augmented โ€” lazy, closes over concrete n_x and n_v +# ============================================================================= + +function build_rhs_augmented(sys::HamiltonianSystem, n_x::Int, n_v::Int) + h, backend = sys.h, sys.backend + return function (du, u, ฮป, t) + v = Common.variable(ฮป) + _check_aug_batch_compat(u, v) # no-op if not matrix or matrix compatible + x, p, _ = _aug_split(u, n_x, n_v) + โˆ‚x, โˆ‚p = Differentiation.hamiltonian_gradient(backend, h, t, x, p, v, Common.cache(ฮป)) + โˆ‚pv = Differentiation.variable_gradient(backend, h, t, x, p, v, Common.cache(ฮป)) + _aug_assign!(du, โˆ‚p, -โˆ‚x, -โˆ‚pv, n_x, n_v) + return nothing + end +end + + +# ============================================================================= +# Base.show +# ============================================================================= + +function Base.show(io::IO, sys::HamiltonianSystem) + println(io, "HamiltonianSystem") + println(io, " time_dependence: ", Traits.time_dependence(sys)) + println(io, " variable_dependence: ", Traits.variable_dependence(sys)) + println(io, " hamiltonian: ", sys.h) + println(io, " backend: ", sys.backend) +end + +# ============================================================================= +# Trait getters +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return the variable costate capability trait of a variable-dependent Hamiltonian system. + +# Arguments +- `sys::HamiltonianSystem`: A Hamiltonian system with variable dependence. + +# Returns +- `SupportsVariableCostate` if the system has `NonFixed` variable dependence. +- `NoVariableCostate` if the system has `Fixed` variable dependence. + +# Notes +- Only `HamiltonianSystem` with `NonFixed` variable dependence supports variable costate integration +- This is because only variable-dependent systems have a variable `v` to differentiate against +- This trait enables the `variable_costate=true` kwarg in Hamiltonian flow calls + +See also: [`CTFlows.Traits.AbstractVariableCostateCapability`](@ref), [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +function Traits.variable_costate_trait( + ::HamiltonianSystem{F, TD, Traits.NonFixed, B} +) where {F, TD, B} + return Traits.SupportsVariableCostate +end diff --git a/src/Systems/hamiltonian_vector_field_system.jl b/src/Systems/hamiltonian_vector_field_system.jl new file mode 100644 index 00000000..1aaa11e2 --- /dev/null +++ b/src/Systems/hamiltonian_vector_field_system.jl @@ -0,0 +1,265 @@ +""" +$(TYPEDEF) + +Concrete `AbstractHamiltonianSystem` wrapping a `HamiltonianVectorField`. + +The system does not store pre-computed RHS closures. Instead, closures are built +lazily by `build_rhs` and `build_oop_rhs` based on the actual initial condition +types, ensuring correct handling of scalar, vector (including length-1), and matrix +inputs with consistent output shapes. + +# Type Parameters +- `F`: concrete type of the wrapped HamiltonianVectorField function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. +- `MD <: AbstractMutabilityTrait`: `InPlace` or `OutOfPlace`. + +# Fields +- `hvf::HamiltonianVectorField{F, TD, VD, MD}`: the underlying Hamiltonian vector field. + +# Example +```julia-repl +julia> using CTFlows.Systems, CTFlows.Common + +julia> hvf = HamiltonianVectorField((x, p) -> (x, -p); autonomous=true, variable=false) +HamiltonianVectorField + time_dependence: Autonomous + variable_dependence: Fixed + mutability: OutOfPlace + function: var"#1" + +julia> sys = HamiltonianVectorFieldSystem(hvf) +HamiltonianVectorFieldSystem + time_dependence: Autonomous + variable_dependence: Fixed + mutability: OutOfPlace + hamiltonian_vector_field: HamiltonianVectorField{var"#1", Autonomous, Fixed, OutOfPlace} +``` + +See also: [`CTFlows.Data.HamiltonianVectorField`](@ref), [`CTFlows.Systems.AbstractHamiltonianSystem`](@ref), [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Systems.build_rhs`](@ref), [`CTFlows.Systems.build_oop_rhs`](@ref). +""" +struct HamiltonianVectorFieldSystem{F<:Function, TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} <: AbstractHamiltonianSystem{TD, VD, Traits.WithoutAD} + hvf::Data.HamiltonianVectorField{F, TD, VD, MD} +end + +# ============================================================================= +# Constructors +# ============================================================================= + +function HamiltonianVectorFieldSystem(hvf::Data.HamiltonianVectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + return HamiltonianVectorFieldSystem{F, TD, VD, MD}(hvf) +end + +# ============================================================================= +# Internal helpers for shape inference and coercion +# ============================================================================= + +""" + _state_dim(x0::Number) = 1 + _state_dim(x0::AbstractVector) = length(x0) + _state_dim(x0::AbstractMatrix) = size(x0, 1) + +Infer the state dimension from the initial condition shape. +""" +_state_dim(::Number) = 1 +_state_dim(x::AbstractVector) = length(x) +_state_dim(x::AbstractMatrix) = size(x, 1) + +""" + _make_coerce(::Number) = only + _make_coerce(::AbstractVector) = identity + _make_coerce(::AbstractMatrix) = identity + +Return a coercion function for the given shape. For scalars, `only` extracts +a single element from a 1-element vector. For arrays, `identity` is a no-op. +""" +_make_coerce(::Number) = only +_make_coerce(::AbstractVector) = identity +_make_coerce(::AbstractMatrix) = identity + +# ============================================================================= +# Internal helpers for split/assign (dispatch on array type) +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Split a combined state `u` into state `x` and costate `p` views for Hamiltonian systems. + +Dispatches on the array type (`AbstractVector` or `AbstractMatrix`) and the known dimension `N` (or `nothing` for runtime inference). + +# Arguments +- `u::AbstractVector` or `AbstractMatrix`: Combined state vector/matrix `[x; p]`. +- `N::Int` or `nothing`: State dimension. If `nothing`, infers as `size(u, 1) รท 2`. + +# Returns +- `Tuple`: Tuple of views `(x_view, p_view)` where: + - For vectors: `x_view = @view(u[1:N])`, `p_view = @view(u[N+1:2N])` + - For matrices: `x_view = @view(u[1:N, :])`, `p_view = @view(u[N+1:2N, :])` + +# Notes +- Internal helper used by RHS builders for Hamiltonian systems. +- The `CTFlowsStaticArrays` extension provides a type-stable method for `StaticVector`. +""" +_ham_split(u::AbstractVector, N::Int) = (@view(u[1:N]), @view(u[N+1:2N])) +_ham_split(u::AbstractMatrix, N::Int) = (@view(u[1:N, :]), @view(u[N+1:2N, :])) + +""" +$(TYPEDSIGNATURES) + +Assign derivatives `dx` and `dp` to the combined derivative `du` for Hamiltonian systems. + +Dispatches on the array type (`AbstractVector` or `AbstractMatrix`) and the known dimension `N` (or `nothing` for runtime inference). + +# Arguments +- `du::AbstractVector` or `AbstractMatrix`: Combined derivative to fill `[dx; dp]`. +- `dx`: State derivative (same shape as state part of `du`). +- `dp`: Costate derivative (same shape as costate part of `du`). +- `N::Int` or `nothing`: State dimension. If `nothing`, infers as `size(du, 1) รท 2`. + +# Returns +- `nothing` + +# Notes +- Internal helper used by RHS builders for Hamiltonian systems. +- Performs in-place assignment using broadcasting. +""" +_ham_assign!(du::AbstractVector, dx, dp, N::Int) = (du[1:N] .= dx; du[N+1:2N] .= dp) +_ham_assign!(du::AbstractMatrix, dx, dp, N::Int) = (du[1:N, :] .= dx; du[N+1:2N, :] .= dp) + +# ============================================================================= +# Public lazy RHS builders +# ============================================================================= + +""" + build_rhs(sys::HamiltonianVectorFieldSystem, x0, p0) -> f!(du, u, ฮป, t) + +Build an in-place RHS closure for the given initial conditions. + +The closure is constructed lazily based on the shapes of `x0` and `p0`, +ensuring correct handling of scalar, vector, and matrix inputs. + +# Arguments +- `sys::HamiltonianVectorFieldSystem`: The Hamiltonian system. +- `x0`: Initial state (scalar, vector, or matrix). +- `p0`: Initial costate (same shape as `x0`). + +# Returns +- `Function`: A closure with signature `(du, u, ฮป, t) -> nothing`. +""" +function build_rhs(sys::HamiltonianVectorFieldSystem{F, TD, VD, Traits.OutOfPlace}, x0, p0) where {F, TD, VD} + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + hvf = sys.hvf + return function (du, u, ฮป, t) + x, p = _ham_split(u, N) + dx, dp = hvf(t, cx(x), cp(p), Common.variable(ฮป)) + _ham_assign!(du, dx, dp, N) + return nothing + end +end + +function build_rhs(sys::HamiltonianVectorFieldSystem{F, TD, VD, Traits.InPlace}, x0, p0) where {F, TD, VD} + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + hvf = sys.hvf + return function (du, u, ฮป, t) + x, p = _ham_split(u, N) + dx, dp = _ham_split(du, N) + hvf(dx, dp, t, cx(x), cp(p), Common.variable(ฮป)) + return nothing + end +end + +""" + build_oop_rhs(sys::HamiltonianVectorFieldSystem, x0, p0) -> f(u, ฮป, t) + +Build an out-of-place RHS closure for the given initial conditions. + +The closure is constructed lazily based on the shapes of `x0` and `p0`, +ensuring correct handling of scalar, vector, and matrix inputs. + +# Arguments +- `sys::HamiltonianVectorFieldSystem`: The Hamiltonian system. +- `x0`: Initial state (scalar, vector, or matrix). +- `p0`: Initial costate (same shape as `x0`). + +# Returns +- `Function`: A closure with signature `(u, ฮป, t) -> du`. +""" +function build_oop_rhs(sys::HamiltonianVectorFieldSystem{F, TD, VD, Traits.OutOfPlace}, x0, p0) where {F, TD, VD} + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + hvf = sys.hvf + return function (u, ฮป, t) + x, p = _ham_split(u, N) + dx, dp = hvf(t, cx(x), cp(p), Common.variable(ฮป)) + return vcat(dx, dp) + end +end + +function build_oop_rhs(sys::HamiltonianVectorFieldSystem{F, TD, VD, Traits.InPlace}, x0, p0) where {F, TD, VD} + N = _state_dim(x0) + cx = _make_coerce(x0) + cp = _make_coerce(p0) + hvf = sys.hvf + is_u0_mutable = ismutable(x0) + if !is_u0_mutable + @warn "InPlace HamiltonianVectorField with immutable u0 (e.g. SVector): consider using an out-of-place function for better performance." + end + return function (u, ฮป, t) + x, p = _ham_split(u, N) + dx, dp = similar(x), similar(p) + hvf(dx, dp, t, cx(x), cp(p), Common.variable(ฮป)) + result = vcat(dx, dp) + if !is_u0_mutable + return typeof(u)(result) + end + return result + end +end + + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a HamiltonianVectorFieldSystem. + +Shows the type name and the wrapped HamiltonianVectorField with its traits. + +# Arguments +- `io::IO`: The IO stream to write to. +- `sys::HamiltonianVectorFieldSystem`: The HamiltonianVectorFieldSystem to display. + +See also: [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref). +""" +function Base.show(io::IO, sys::HamiltonianVectorFieldSystem{F, TD, VD, MD}) where {F, TD, VD, MD} + println(io, "HamiltonianVectorFieldSystem") + wraps = "HamiltonianVectorField: $(Data._td_label(TD)), $(Data._vd_label(VD)), $(Data._md_label(MD))" + print(io, " wraps: ", wraps) +end + +""" +$(TYPEDSIGNATURES) + +Display a HamiltonianVectorFieldSystem in the REPL with text/plain MIME type. + +Delegates to the compact show method. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `sys::HamiltonianVectorFieldSystem`: The HamiltonianVectorFieldSystem to display. + +See also: [`CTFlows.Systems.HamiltonianVectorFieldSystem`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", sys::HamiltonianVectorFieldSystem) + show(io, sys) +end diff --git a/src/Systems/vector_field_system.jl b/src/Systems/vector_field_system.jl new file mode 100644 index 00000000..40b011e0 --- /dev/null +++ b/src/Systems/vector_field_system.jl @@ -0,0 +1,329 @@ +""" +$(TYPEDEF) + +Concrete `AbstractSystem` wrapping a `VectorField`. The variable for +`NonFixed` vector fields is **not** stored here; it is passed at flow-call +time via the `variable` kwarg and threaded through `ODEProblem`'s `p` slot +wrapped in a `Common.ODEParameters` struct. + +# Fields +- `vf::VectorField{F, TD, VD, MD}`: the underlying vector field. +- `rhs::RHS`: the pre-computed in-place right-hand side closure with signature `(du, u, p, t) -> nothing`. +- `rhs_oop::OOPROHS`: the pre-computed out-of-place right-hand side closure with signature `(u, p, t) -> du`. +- `rhs_oop_finalize::FINRHS`: the finalize closure for in-place vector fields with immutable initial conditions, or `nothing` for out-of-place vector fields. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Systems, CTFlows.Common + +julia> vf = VectorField(x -> -x; autonomous=true, variable=false) +VectorField + time_dependence: Autonomous + variable_dependence: Fixed + mutability: OutOfPlace + function: var"#1" + +julia> sys = VectorFieldSystem(vf) +VectorFieldSystem + time_dependence: Autonomous + variable_dependence: Fixed + mutability: OutOfPlace + vector_field: VectorField{var"#1", Autonomous, Fixed, OutOfPlace} +\`\`\` + +See also: [`CTFlows.Data.VectorField`](@ref), [`CTFlows.Traits.TimeDependence`](@ref), [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Common.ODEParameters`](@ref). +""" +struct VectorFieldSystem{F<:Function, TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait, RHS<:Function, OOPROHS<:Function, FINRHS} <: AbstractStateSystem{TD, VD} + vf::Data.VectorField{F, TD, VD, MD} + rhs::RHS + rhs_oop::OOPROHS + rhs_oop_finalize::FINRHS +end + +# ============================================================================= +# Constructors +# ============================================================================= + +function VectorFieldSystem(vf::Data.VectorField{F, TD, VD, Traits.OutOfPlace}) where {F, TD, VD} + rhs = _build_rhs_vf_oop(Traits.OutOfPlace, vf) + rhs_oop = _build_oop_rhs_vf_oop(Traits.OutOfPlace, vf) + rhs_oop_finalize = nothing + return VectorFieldSystem{F, TD, VD, Traits.OutOfPlace, typeof(rhs), typeof(rhs_oop), Nothing}(vf, rhs, rhs_oop, rhs_oop_finalize) +end + +function VectorFieldSystem(vf::Data.VectorField{F, TD, VD, Traits.InPlace}) where {F, TD, VD} + rhs = _build_rhs_vf_ip(Traits.InPlace, vf) + rhs_oop = _build_oop_rhs_vf_ip(Traits.InPlace, vf) + rhs_oop_finalize = _build_finalize_rhs_vf_ip(Traits.InPlace, vf) + return VectorFieldSystem{F, TD, VD, Traits.InPlace, typeof(rhs), typeof(rhs_oop), typeof(rhs_oop_finalize)}(vf, rhs, rhs_oop, rhs_oop_finalize) +end + +# ============================================================================= +# Guard for unsupported combinations +# ============================================================================= + +""" + _check_vf_scalar_inplace(sys::VectorFieldSystem{F, TD, VD, Traits.InPlace}, u0::Number) + +Raise an error if an in-place vector field is used with a scalar initial condition. +This combination is unsupported because in-place functions require mutable arrays. + +# Arguments +- `sys::VectorFieldSystem`: The vector field system. +- `u0`: The initial condition. + +# Throws +- `ArgumentError` if `sys` is in-place and `u0` is a scalar. +""" +function _check_vf_scalar_inplace(sys::VectorFieldSystem{F, TD, VD, Traits.InPlace}, u0::Number) where {F, TD, VD} + throw(ArgumentError( + "InPlace VectorField with scalar u0 is unsupported. " * + "Scalar initial conditions require out-of-place vector fields. " * + "Consider using an out-of-place VectorField or a vector-valued initial condition." + )) +end + +_check_vf_scalar_inplace(::AbstractSystem, ::Any) = nothing + +# ============================================================================= +# Internal helpers: RHS builders +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Build the in-place RHS closure for an out-of-place vector field. + +For out-of-place vector fields, the RHS uses the broadcasting assignment to copy +the result into the destination array. + +# Arguments +- `::Type{OutOfPlace}`: The out-of-place mutability trait. +- `vf::Data.VectorField`: The vector field data structure. + +# Returns +- `Function`: A closure with signature `(du, u, ฮป, t) -> nothing`. +""" +function _build_rhs_vf_oop(::Type{Traits.OutOfPlace}, vf::Data.VectorField) + return (du, u, ฮป, t) -> (du .= vf(t, u, Common.variable(ฮป)); nothing) +end + +""" +$(TYPEDSIGNATURES) + +Build the in-place RHS closure for an in-place vector field. + +For in-place vector fields, the RHS calls the vector field directly with the +destination array as the first argument. + +# Arguments +- `::Type{InPlace}`: The in-place mutability trait. +- `vf::Data.VectorField`: The vector field data structure. + +# Returns +- `Function`: A closure with signature `(du, u, ฮป, t) -> nothing`. +""" +function _build_rhs_vf_ip(::Type{Traits.InPlace}, vf::Data.VectorField) + return (du, u, ฮป, t) -> (vf(du, t, u, Common.variable(ฮป)); nothing) +end + +""" +$(TYPEDSIGNATURES) + +Build the out-of-place RHS closure for an out-of-place vector field. + +For out-of-place vector fields, the RHS directly calls the vector field. + +# Arguments +- `::Type{OutOfPlace}`: The out-of-place mutability trait. +- `vf::Data.VectorField`: The vector field data structure. + +# Returns +- `Function`: A closure with signature `(u, ฮป, t) -> du`. +""" +function _build_oop_rhs_vf_oop(::Type{Traits.OutOfPlace}, vf::Data.VectorField) + return (u, ฮป, t) -> vf(t, u, Common.variable(ฮป)) +end + +""" +$(TYPEDSIGNATURES) + +Build the out-of-place RHS closure for an in-place vector field. + +For in-place vector fields, the RHS allocates a new array and calls the vector +field with it as the destination. + +# Arguments +- `::Type{InPlace}`: The in-place mutability trait. +- `vf::Data.VectorField`: The vector field data structure. + +# Returns +- `Function`: A closure with signature `(u, ฮป, t) -> du`. +""" +function _build_oop_rhs_vf_ip(::Type{Traits.InPlace}, vf::Data.VectorField) + return function (u, ฮป, t) + dx = similar(u) + vf(dx, t, u, Common.variable(ฮป)) + return dx + end +end + +""" +$(TYPEDSIGNATURES) + +Build the finalize RHS closure for an in-place vector field with immutable u0. + +This is used when the initial condition is immutable (e.g., SVector), requiring +a conversion to the appropriate type after the in-place computation. + +# Arguments +- `::Type{InPlace}`: The in-place mutability trait. +- `vf::Data.VectorField`: The vector field data structure. + +# Returns +- `Function`: A closure with signature `(u, ฮป, t) -> du` that returns the result + converted to the same type as `u`. +""" +function _build_finalize_rhs_vf_ip(::Type{Traits.InPlace}, vf::Data.VectorField) + return function (u, ฮป, t) + dx = similar(u) + vf(dx, t, u, Common.variable(ฮป)) + return typeof(u)(dx) + end +end + +""" +$(TYPEDSIGNATURES) + +In-place right-hand side for a `VectorFieldSystem`. Returns the pre-computed +closure stored in the system, which has signature `(du, u, p, t) -> nothing` and +uses the uniform `(t, x, v)` call on the underlying `VectorField`, where `p` +is a `Common.ODEParameters` wrapper containing the variable (or `nothing` +for `Fixed` systems). + +# Arguments +- `sys::VectorFieldSystem`: The system for which to return the RHS function. + +# Returns +- `Function`: The pre-computed closure with signature `(du, u, p, t) -> nothing`. + +# Example +\`\`\`julia +using CTFlows.Systems, CTFlows.Common + +vf = VectorField(x -> -x; autonomous=true, variable=false) +sys = VectorFieldSystem(vf) +rhs = Systems.rhs(sys) + +du = zeros(2) +u = [1.0, 2.0] +p = Common.ODEParameters(nothing) +rhs(du, u, p, 0.0) +# du is now [-1.0, -2.0] +\`\`\` + +# Notes +- The closure is computed once at construction time for performance. +- Multiple calls to `rhs` return the same function object. +- The closure reads `variable(p)` to access the actual variable value. + +See also: [`CTFlows.Systems.VectorFieldSystem`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref), [`CTFlows.Common.ODEParameters`](@ref). +""" +function rhs(sys::VectorFieldSystem) + return sys.rhs +end + +""" +$(TYPEDSIGNATURES) + +Out-of-place right-hand side for an `OutOfPlace` `VectorFieldSystem`. + +Returns the pre-computed closure with signature `(u, p, t) -> du`. The optional +`is_u0_mutable` argument is accepted but ignored: for out-of-place vector fields +`rhs_oop` is always the correct callable regardless of u0 mutability. + +# Arguments +- `sys::VectorFieldSystem{..., OutOfPlace, ...}`: The out-of-place system. +- `::Bool`: Ignored. Accepted for API uniformity with the `InPlace` method. + +# Returns +- `Function`: The pre-computed closure with signature `(u, p, t) -> du`. + +See also: [`CTFlows.Systems.VectorFieldSystem`](@ref), [`CTFlows.Systems.rhs`](@ref). +""" +function rhs_oop(sys::VectorFieldSystem{F, TD, VD, Traits.OutOfPlace, RHS, OOPROHS, Nothing}, ::Bool = true) where {F, TD, VD, RHS, OOPROHS} + return sys.rhs_oop +end + +""" +$(TYPEDSIGNATURES) + +Out-of-place right-hand side for an `InPlace` `VectorFieldSystem`, dispatching on u0 mutability. + +For in-place vector fields the appropriate callable depends on whether the initial +condition `u0` is mutable: +- **`is_u0_mutable = true`** (default): returns `rhs_oop`, which allocates a mutable + buffer and returns it. Correct when `u0` is a `Vector` or similar mutable array. +- **`is_u0_mutable = false`**: returns `rhs_oop_finalize`, which allocates a mutable + buffer, fills it via the in-place call, then converts back to `typeof(u)`. Correct + when `u0` is immutable (e.g. `StaticArrays.SVector`). A one-time performance + warning is emitted in this case. + +# Arguments +- `sys::VectorFieldSystem{..., InPlace, ...}`: The in-place system. +- `is_u0_mutable::Bool`: `true` if u0 is mutable, `false` if immutable. Defaults to `true`. + +# Returns +- `Function`: The appropriate closure with signature `(u, p, t) -> du`. + +# Notes +- Prefer out-of-place vector fields when u0 is a `StaticArrays.SVector` for best performance. + +See also: [`CTFlows.Systems.VectorFieldSystem`](@ref), [`CTFlows.Systems.rhs`](@ref). +""" +function rhs_oop(sys::VectorFieldSystem{F, TD, VD, Traits.InPlace, RHS, OOPROHS, FINRHS}, is_u0_mutable::Bool = true) where {F, TD, VD, RHS, OOPROHS, FINRHS} + is_u0_mutable && return sys.rhs_oop + @warn "InPlace VectorField with immutable u0 (e.g. SVector): consider using an out-of-place function for better performance." + return sys.rhs_oop_finalize +end + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a VectorFieldSystem. + +Shows the type name and the wrapped VectorField with its traits. + +# Arguments +- `io::IO`: The IO stream to write to. +- `sys::VectorFieldSystem`: The VectorFieldSystem to display. + +See also: [`CTFlows.Systems.VectorFieldSystem`](@ref). +""" +function Base.show(io::IO, sys::VectorFieldSystem{F, TD, VD, MD, RHS, OOPROHS, FINRHS}) where {F, TD, VD, MD, RHS, OOPROHS, FINRHS} + println(io, "VectorFieldSystem") + wraps = "VectorField: $(Data._td_label(TD)), $(Data._vd_label(VD)), $(Data._md_label(MD))" + print(io, " wraps: ", wraps) +end + +""" +$(TYPEDSIGNATURES) + +Display a VectorFieldSystem in the REPL with text/plain MIME type. + +Delegates to the compact show method. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `sys::VectorFieldSystem`: The VectorFieldSystem to display. + +See also: [`CTFlows.Systems.VectorFieldSystem`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", sys::VectorFieldSystem) + show(io, sys) +end \ No newline at end of file diff --git a/src/Traits/Traits.jl b/src/Traits/Traits.jl new file mode 100644 index 00000000..da20c556 --- /dev/null +++ b/src/Traits/Traits.jl @@ -0,0 +1,49 @@ +module Traits + +# ============================================================================== +# External package imports +# ============================================================================== + +using Reexport +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +import CTModels.OCP + +# ============================================================================== +# Includes +# ============================================================================== + +include(joinpath(@__DIR__, "helpers.jl")) +include(joinpath(@__DIR__, "abstract.jl")) +include(joinpath(@__DIR__, "mode.jl")) +include(joinpath(@__DIR__, "content.jl")) +include(joinpath(@__DIR__, "ad.jl")) +include(joinpath(@__DIR__, "variable_costate.jl")) +include(joinpath(@__DIR__, "mutability.jl")) +include(joinpath(@__DIR__, "time_dependence.jl")) +include(joinpath(@__DIR__, "variable_dependence.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +@reexport import CTModels.OCP: Autonomous, NonAutonomous, TimeDependence +@reexport import CTModels.OCP: is_autonomous, is_nonautonomous, is_variable, is_nonvariable, has_variable + +export AbstractTrait +export AbstractModeTrait, AbstractContentTrait +export AbstractMutabilityTrait +export AbstractADTrait +export AbstractVariableCostateCapability +export PointTrait, TrajectoryTrait +export StateTrait, HamiltonianTrait, AugmentedHamiltonianTrait +export InPlace, OutOfPlace +export WithAD, WithoutAD +export SupportsVariableCostate, NoVariableCostate +export VariableDependence, Fixed, NonFixed +export ad_trait, variable_costate_trait +export is_inplace, is_outofplace +export has_time_dependence_trait, time_dependence, has_mutability_trait, mutability_trait +export has_variable_dependence_trait, variable_dependence + +end # module Traits diff --git a/src/Traits/abstract.jl b/src/Traits/abstract.jl new file mode 100644 index 00000000..0d823b30 --- /dev/null +++ b/src/Traits/abstract.jl @@ -0,0 +1,51 @@ +""" +$(TYPEDEF) + +Abstract base type for trait markers in CTFlows. + +Traits are empty marker types used as type parameters to encode configuration +properties at compile time. Unlike tags (which mark extension implementations), +traits encode semantic properties of the configuration itself (e.g., integration +mode, content type, mutability). + +# Trait Pattern + +Traits are used as type parameters in abstract configuration types to enable +compile-time dispatch without runtime type checks. For example, `AbstractConfig` +uses `PointTrait` vs `TrajectoryTrait` to distinguish integration modes, and +`StateTrait` vs `HamiltonianTrait` to distinguish content types. + +All concrete trait types are empty structs with no fields, making them zero-cost +at runtime. + +# Interface Requirements + +Concrete trait subtypes should: +- Be empty structs with no fields (pure markers) +- Subtype an intermediate abstract trait category (e.g., `AbstractModeTrait`) +- Be used as type parameters in configuration types + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> PointTrait <: Traits.AbstractTrait +true + +julia> PointTrait <: Traits.AbstractModeTrait +true + +julia> # Used as type parameters in configs: +julia> StatePointConfig <: CTFlows.Configs.AbstractConfig{<:Any, PointTrait, StateTrait} +true +\`\`\` + +# Notes +- Traits are distinct from tags: tags mark extension implementations (e.g., `SciMLTag`), + while traits encode configuration semantics (e.g., `PointTrait`) +- All trait types have zero runtime overhead (empty structs) +- The trait pattern enables static dispatch on configuration properties + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.AbstractModeTrait`](@ref), [`CTFlows.Traits.AbstractContentTrait`](@ref), [`CTFlows.Traits.AbstractMutabilityTrait`](@ref). +""" +abstract type AbstractTrait end diff --git a/src/Traits/ad.jl b/src/Traits/ad.jl new file mode 100644 index 00000000..6986d588 --- /dev/null +++ b/src/Traits/ad.jl @@ -0,0 +1,127 @@ +""" +$(TYPEDEF) + +Abstract base type for automatic differentiation capability traits. + +AD traits encode whether a system supports automatic differentiation for gradient +computation. This distinction enables compile-time dispatch for cache preparation, +derivative computation, and other AD-related operations. + +Common use cases include: +- Hamiltonian systems: distinguishing between scalar Hamiltonians with AD backends + and pre-computed Hamiltonian vector fields +- General optimization: marking systems that can compute gradients via AD vs. those + with manual derivative implementations +- Cache preparation: enabling static dispatch for AD-specific cache initialization + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> WithAD() isa Traits.AbstractADTrait +true + +julia> WithoutAD() isa Traits.AbstractADTrait +true +\`\`\` + +# Notes +- AD traits are used as type parameters in system types to enable static dispatch +- `WithAD` indicates the system carries differentiable functions and an AD backend +- `WithoutAD` indicates the system uses pre-computed derivatives or manual implementations +- The specific meaning depends on the system type and context + +See also: [`CTFlows.Traits.WithAD`](@ref), [`CTFlows.Traits.WithoutAD`](@ref), [`CTFlows.Common.AbstractCache`](@ref). +""" +abstract type AbstractADTrait <: AbstractTrait end + +""" +$(TYPEDEF) + +Trait for systems with automatic differentiation support. + +Indicates that a system carries differentiable functions and an AD backend, +enabling automatic computation of derivatives. Such systems typically require +cache preparation before operations that need gradients or Jacobians. + +Common use cases include: +- Hamiltonian systems: scalar Hamiltonians where the vector field is computed via AD +- Optimization: objective functions where gradients are computed via AD +- General systems: any differentiable function that benefits from automatic gradient computation + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> with = WithAD() +WithAD() + +julia> with isa Traits.AbstractADTrait +true +\`\`\` + +# Notes +- Used as a type parameter in system types to enable AD-based derivative computation +- Systems with `WithAD` typically require cache preparation via the backend's `prepare_cache` method +- The cache is passed through parameters during integration or evaluation +- The specific operations enabled depend on the system type and AD backend + +See also: [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Traits.WithoutAD`](@ref), [`CTFlows.Common.AbstractCache`](@ref). +""" +struct WithAD <: AbstractADTrait end # system carries H + AD backend + +""" +$(TYPEDEF) + +Trait for systems without automatic differentiation support. + +Indicates that a system uses pre-computed derivatives or manual implementations, +without requiring AD or cache preparation. This is the traditional mode where +derivatives are provided explicitly by the user or computed offline. + +Common use cases include: +- Hamiltonian systems: pre-computed Hamiltonian vector fields provided manually +- Optimization: manually implemented gradient functions +- General systems: any system where derivatives are known analytically or computed externally + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> without = WithoutAD() +WithoutAD() + +julia> without isa Traits.AbstractADTrait +true +\`\`\` + +# Notes +- Used as a type parameter in system types for derivative-based systems without AD +- No cache preparation is required for `WithoutAD` systems +- This is the default mode for systems with pre-computed derivatives +- The specific derivative implementations depend on the system type + +See also: [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Traits.WithAD`](@ref). +""" +struct WithoutAD <: AbstractADTrait end # system carries HVF directly + +""" +$(TYPEDSIGNATURES) + +Return the automatic differentiation capability trait of a system or flow. + +# Arguments +- `obj`: Any object (default implementation returns `WithoutAD`). + +# Returns +- `Type{<:AbstractADTrait}`: The AD capability trait, either `WithAD` or `WithoutAD`. + +# Notes +- Default implementation returns `WithoutAD` for all objects +- Specialized implementations on system types return the appropriate trait based on the system's AD support +- Used for dispatch in cache preparation, derivative computation, and augmented integration +- The specific operations enabled by the trait depend on the system type + +See also: [`CTFlows.Traits.AbstractADTrait`](@ref), [`CTFlows.Traits.WithAD`](@ref), [`CTFlows.Traits.WithoutAD`](@ref). +""" +ad_trait(::Any) = WithoutAD diff --git a/src/Traits/content.jl b/src/Traits/content.jl new file mode 100644 index 00000000..260873d8 --- /dev/null +++ b/src/Traits/content.jl @@ -0,0 +1,110 @@ +""" +$(TYPEDEF) + +Abstract base type for content traits (State vs Hamiltonian). + +Content traits encode the content type in configuration types, distinguishing +between state-only configurations (no costate) and Hamiltonian configurations +(state + costate). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> StateTrait <: Traits.AbstractContentTrait +true + +julia> HamiltonianTrait <: Traits.AbstractContentTrait +true + +julia> # Used in configuration type parameters: +julia> StatePointConfig <: CTFlows.Configs.AbstractConfig{<:Any, <:Traits.AbstractModeTrait, StateTrait} +true +\`\`\` + +# Notes +- Content traits are used as the third type parameter in `AbstractConfigWithMaC` +- State trait indicates configurations with only state variables (no costate) +- Hamiltonian trait indicates configurations with both state and costate variables + +See also: [`CTFlows.Traits.StateTrait`](@ref), [`CTFlows.Traits.HamiltonianTrait`](@ref), [`CTFlows.Configs.AbstractConfig`](@ref). +""" +abstract type AbstractContentTrait <: AbstractTrait end + +""" +$(TYPEDEF) + +Trait for state content (no costate). + +Used as a type parameter in `AbstractConfig` to indicate state-only configurations, +which contain only state variables without associated costate variables. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> st = StateTrait() +StateTrait() + +julia> st isa Traits.AbstractContentTrait +true + +julia> # Used in state-only configurations: +julia> StatePointConfig <: CTFlows.Configs.AbstractConfig{<:Any, <:Traits.AbstractModeTrait, StateTrait} +true +\`\`\` + +# Notes +- State configurations store only `x0` (initial state) +- The `initial_costate` accessor throws a `PreconditionError` for state configurations +- This mode is suitable for standard ODE integration without adjoint variables + +See also: [`CTFlows.Traits.HamiltonianTrait`](@ref), [`CTFlows.Traits.AbstractContentTrait`](@ref), [`CTFlows.Configs.StatePointConfig`](@ref). +""" +struct StateTrait <: AbstractContentTrait end + +""" +$(TYPEDEF) + +Trait for Hamiltonian content (state + costate). + +Used as a type parameter in `AbstractConfig` to indicate Hamiltonian configurations, +which contain both state variables and associated costate (adjoint) variables. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> ham = HamiltonianTrait() +HamiltonianTrait() + +julia> ham isa Traits.AbstractContentTrait +true + +julia> # Used in Hamiltonian configurations: +julia> HamiltonianPointConfig <: CTFlows.Configs.AbstractConfig{<:Any, <:Traits.AbstractModeTrait, HamiltonianTrait} +true +\`\`\` + +# Notes +- Hamiltonian configurations store both `x0` (initial state) and `p0` (initial costate) +- The `initial_condition` accessor returns `vcat(x0, p0)` for Hamiltonian configurations +- This mode is suitable for optimal control problems with Pontryagin's maximum principle + +See also: [`CTFlows.Traits.StateTrait`](@ref), [`CTFlows.Traits.AbstractContentTrait`](@ref), [`CTFlows.Configs.HamiltonianPointConfig`](@ref). +""" +struct HamiltonianTrait <: AbstractContentTrait end + +""" +$(TYPEDEF) + +Trait marker for augmented Hamiltonian systems, where the Hamiltonian includes an augmented variable (e.g., a parameter or control variable) in addition to state and costate variables. + +# Notes +- Used in conjunction with [`CTFlows.Configs.AbstractAugmentedHamiltonianConfig`](@ref) to specify that a configuration is for an augmented Hamiltonian system. +- Subtypes [`CTFlows.Traits.AbstractContentTrait`](@ref). +- Used to distinguish augmented Hamiltonian systems from standard Hamiltonian systems in trait-based dispatch. + +See also: [`CTFlows.Traits.HamiltonianTrait`](@ref), [`CTFlows.Configs.AbstractAugmentedHamiltonianConfig`](@ref), [`CTFlows.Traits.AbstractContentTrait`](@ref). +""" +struct AugmentedHamiltonianTrait <: AbstractContentTrait end diff --git a/src/Traits/helpers.jl b/src/Traits/helpers.jl new file mode 100644 index 00000000..1b878697 --- /dev/null +++ b/src/Traits/helpers.jl @@ -0,0 +1,26 @@ +""" + _caller_function_name() -> Symbol + +Return the name of the calling function by inspecting the stacktrace. + +This is used to provide better error messages in trait check functions +without requiring an explicit `source_method` argument. + +# Returns +- `Symbol`: The name of the calling function, or `:unknown` if it cannot be determined. +""" +function _caller_function_name() + stack = stacktrace() + for frame in stack + func_name = frame.func + func_str = string(func_name) + if func_str != "_caller_function_name" && + !startswith(func_str, "#") && + func_str != "has_time_dependence_trait" && + func_str != "has_variable_dependence_trait" && + func_str != "has_mutability_trait" + return func_name + end + end + return :unknown +end diff --git a/src/Traits/mode.jl b/src/Traits/mode.jl new file mode 100644 index 00000000..ce013d68 --- /dev/null +++ b/src/Traits/mode.jl @@ -0,0 +1,96 @@ +""" +$(TYPEDEF) + +Abstract base type for mode traits (Point vs Trajectory). + +Mode traits encode the integration mode in configuration types, distinguishing +between point-to-point integration (single endpoint evaluation) and trajectory +integration (full time evolution). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> PointTrait <: Traits.AbstractModeTrait +true + +julia> TrajectoryTrait <: Traits.AbstractModeTrait +true + +julia> # Used in configuration type parameters: +julia> StatePointConfig <: CTFlows.Configs.AbstractConfig{<:Any, PointTrait, <:Traits.AbstractContentTrait} +true +\`\`\` + +# Notes +- Mode traits are used as the second type parameter in `AbstractConfigWithMaC` +- Point mode indicates integration from a single initial condition to a specific final time +- Trajectory mode indicates integration over a continuous time interval + +See also: [`CTFlows.Traits.PointTrait`](@ref), [`CTFlows.Traits.TrajectoryTrait`](@ref), [`CTFlows.Configs.AbstractConfig`](@ref). +""" +abstract type AbstractModeTrait <: AbstractTrait end + +""" +$(TYPEDEF) + +Trait for point integration mode (single endpoint evaluation). + +Used as a type parameter in `AbstractConfig` to indicate point integration, +which computes the solution at a specific final time from a single initial condition. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> pt = PointTrait() +PointTrait() + +julia> pt isa Traits.AbstractModeTrait +true + +julia> # Used in point-to-point configurations: +julia> StatePointConfig <: CTFlows.Configs.AbstractConfig{<:Any, PointTrait, <:Traits.AbstractContentTrait} +true +\`\`\` + +# Notes +- Point mode configurations store `t0` and `tf` as separate fields +- This mode is suitable for boundary value problems and shooting methods +- The `tspan` accessor returns `(c.t0, c.tf)` for point configurations + +See also: [`CTFlows.Traits.TrajectoryTrait`](@ref), [`CTFlows.Traits.AbstractModeTrait`](@ref), [`CTFlows.Configs.StatePointConfig`](@ref). +""" +struct PointTrait <: AbstractModeTrait end + +""" +$(TYPEDEF) + +Trait for trajectory integration mode (full time evolution). + +Used as a type parameter in `AbstractConfig` to indicate trajectory integration, +which computes the full solution trajectory over a continuous time interval. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> traj = TrajectoryTrait() +TrajectoryTrait() + +julia> traj isa Traits.AbstractModeTrait +true + +julia> # Used in trajectory configurations: +julia> StateTrajectoryConfig <: CTFlows.Configs.AbstractConfig{<:Any, TrajectoryTrait, <:Traits.AbstractContentTrait} +true +\`\`\` + +# Notes +- Trajectory mode configurations store `tspan` as a tuple field +- This mode is suitable for generating full time evolution and visualization +- The `tspan` accessor returns `c.tspan` directly for trajectory configurations + +See also: [`CTFlows.Traits.PointTrait`](@ref), [`CTFlows.Traits.AbstractModeTrait`](@ref), [`CTFlows.Configs.StateTrajectoryConfig`](@ref). +""" +struct TrajectoryTrait <: AbstractModeTrait end diff --git a/src/Traits/mutability.jl b/src/Traits/mutability.jl new file mode 100644 index 00000000..224397de --- /dev/null +++ b/src/Traits/mutability.jl @@ -0,0 +1,180 @@ +""" +$(TYPEDEF) + +Abstract trait for mutability characteristics of function evaluation. + +Distinguishes between in-place functions (which modify a pre-allocated buffer) +and out-of-place functions (which allocate and return new results). + +Subtypes must implement: +- `InPlace`: For functions that write to a mutable buffer +- `OutOfPlace`: For functions that return newly allocated results + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> InPlace() isa AbstractMutabilityTrait +true + +julia> OutOfPlace() isa AbstractMutabilityTrait +true +\`\`\` + +See also: [`CTFlows.Traits.InPlace`](@ref), [`CTFlows.Traits.OutOfPlace`](@ref). +""" +abstract type AbstractMutabilityTrait <: AbstractTrait end + +""" +$(TYPEDEF) + +Trait for in-place function evaluation. + +Indicates that a function modifies a pre-allocated buffer passed as an argument, +rather than allocating and returning a new result. This pattern is used for +performance-critical code where avoiding allocations is important. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> ip = InPlace() +InPlace() + +julia> ip isa AbstractMutabilityTrait +true +\`\`\` + +See also: [`CTFlows.Traits.AbstractMutabilityTrait`](@ref), [`CTFlows.Traits.OutOfPlace`](@ref). +""" +struct InPlace <: AbstractMutabilityTrait end + +""" +$(TYPEDEF) + +Trait for out-of-place function evaluation. + +Indicates that a function allocates and returns a new result, rather than +modifying a pre-allocated buffer. This is the default pattern in Julia and +is suitable for most use cases. + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> oop = OutOfPlace() +OutOfPlace() + +julia> oop isa AbstractMutabilityTrait +true +\`\`\` + +See also: [`CTFlows.Traits.AbstractMutabilityTrait`](@ref), [`CTFlows.Traits.InPlace`](@ref). +""" +struct OutOfPlace <: AbstractMutabilityTrait end + +""" +$(TYPEDSIGNATURES) + +Check if the object has the mutability trait. + +This fallback method throws an error indicating the object does not support +mutability queries. Concrete types that have the trait should implement +`has_mutability_trait(obj::MyType) = true`. + +The calling function name is automatically detected from the stacktrace +for better error messages. + +# Arguments +- `obj::Any`: The object to check. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): Always, indicating the object does not have the trait. + +See also: [`CTFlows.Traits.AbstractMutabilityTrait`](@ref), [`CTFlows.Traits.mutability_trait`](@ref). +""" +function has_mutability_trait(obj::Any) + source_method = _caller_function_name() + throw(Exceptions.IncorrectArgument( + "Cannot call $(source_method) on object of type $(typeof(obj)): no mutability trait"; + suggestion = "Implement has_mutability_trait(obj::$(typeof(obj))) = true and mutability_trait(obj::$(typeof(obj))) to enable mutability trait support.", + context = "Mutability trait not available", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the mutability trait value for the object. + +This fallback method throws an error indicating the method is not implemented. +Concrete types that have the trait should implement `mutability_trait(obj::MyType)` +to return the specific trait value (`InPlace` or `OutOfPlace`). + +# Arguments +- `obj::Any`: The object to query. + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): Always, indicating the method must be implemented. + +See also: [`CTFlows.Traits.AbstractMutabilityTrait`](@ref), [`CTFlows.Traits.has_mutability_trait`](@ref). +""" +function mutability_trait(obj::Any) + has_mutability_trait(obj) + throw(Exceptions.NotImplemented( + "mutability_trait not implemented for $(typeof(obj))"; + required_method = "mutability_trait(obj::$(typeof(obj)))", + suggestion = "Implement mutability_trait for your concrete object type to return the specific mutability trait (InPlace or OutOfPlace).", + context = "Mutability trait - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return true if the object uses in-place function evaluation. + +Checks that the object has the mutability trait, then returns true +if `mutability_trait(obj)` is `InPlace`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object uses in-place evaluation. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support mutability queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `mutability_trait` is not implemented for the object type. + +See also: [`CTFlows.Traits.AbstractMutabilityTrait`](@ref), [`CTFlows.Traits.mutability_trait`](@ref). +""" +function is_inplace(obj::Any) + has_mutability_trait(obj) + return mutability_trait(obj) === InPlace +end + +""" +$(TYPEDSIGNATURES) + +Return true if the object uses out-of-place function evaluation. + +Checks that the object has the mutability trait, then returns true +if `mutability_trait(obj)` is `OutOfPlace`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object uses out-of-place evaluation. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support mutability queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `mutability_trait` is not implemented for the object type. + +See also: [`CTFlows.Traits.AbstractMutabilityTrait`](@ref), [`CTFlows.Traits.mutability_trait`](@ref). +""" +function is_outofplace(obj::Any) + has_mutability_trait(obj) + return mutability_trait(obj) === OutOfPlace +end diff --git a/src/Traits/time_dependence.jl b/src/Traits/time_dependence.jl new file mode 100644 index 00000000..13b6be2c --- /dev/null +++ b/src/Traits/time_dependence.jl @@ -0,0 +1,105 @@ +""" +$(TYPEDSIGNATURES) + +Check if the object has the time-dependence trait. + +This fallback method throws an error indicating the object does not support +time-dependence queries. Concrete types that have the trait should implement +`has_time_dependence_trait(obj::MyType) = true`. + +The calling function name is automatically detected from the stacktrace +for better error messages. + +# Arguments +- `obj::Any`: The object to check. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): Always, indicating the object does not have the trait. + +See also: [`CTModels.OCP.TimeDependence`](@extref), [`CTFlows.Traits.time_dependence`](@ref). +""" +function has_time_dependence_trait(obj::Any) + source_method = _caller_function_name() + throw(Exceptions.IncorrectArgument( + "Cannot call $(source_method) on object of type $(typeof(obj)): no time-dependence trait"; + suggestion = "Implement has_time_dependence_trait(obj::$(typeof(obj))) = true and time_dependence(obj::$(typeof(obj))) to enable time-dependence trait support.", + context = "Time-dependence trait not available", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the time-dependence trait value for the object. + +This fallback method throws an error indicating the method is not implemented. +Concrete types that have the trait should implement `time_dependence(obj::MyType)` +to return the specific trait value (`Autonomous` or `NonAutonomous`). + +# Arguments +- `obj::Any`: The object to query. + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): Always, indicating the method must be implemented. + +See also: [`CTModels.OCP.TimeDependence`](@extref), [`CTFlows.Traits.has_time_dependence_trait`](@ref). +""" +function time_dependence(obj::Any) + has_time_dependence_trait(obj) + throw(Exceptions.NotImplemented( + "time_dependence not implemented for $(typeof(obj))"; + required_method = "time_dependence(obj::$(typeof(obj)))", + suggestion = "Implement time_dependence for your concrete object type to return the specific time-dependence trait (Autonomous or NonAutonomous).", + context = "Time-dependence trait - required method implementation", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return true if the object is autonomous (time-independent). + +Checks that the object has the time-dependence trait, then returns true +if `time_dependence(obj)` is `Autonomous`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object is autonomous. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support time-dependence queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `time_dependence` is not implemented for the object type. + +See also: [`CTModels.OCP.TimeDependence`](@extref), [`CTFlows.Traits.time_dependence`](@ref). +""" +function OCP.is_autonomous(obj::Any) + has_time_dependence_trait(obj) + return time_dependence(obj) === OCP.Autonomous +end + +""" +$(TYPEDSIGNATURES) + +Return true if the object is non-autonomous (time-dependent). + +Checks that the object has the time-dependence trait, then returns true +if `time_dependence(obj)` is `NonAutonomous`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object is non-autonomous. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support time-dependence queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `time_dependence` is not implemented for the object type. + +See also: [`CTModels.OCP.TimeDependence`](@extref), [`CTFlows.Traits.time_dependence`](@ref). +""" +function OCP.is_nonautonomous(obj::Any) + has_time_dependence_trait(obj) + return time_dependence(obj) === OCP.NonAutonomous +end diff --git a/src/Traits/variable_costate.jl b/src/Traits/variable_costate.jl new file mode 100644 index 00000000..87e20885 --- /dev/null +++ b/src/Traits/variable_costate.jl @@ -0,0 +1,126 @@ +""" +$(TYPEDEF) + +Abstract base type for variable costate capability traits. + +Variable costate capability traits encode whether a system or flow can integrate +augmented variables (e.g., parameters, controls) and compute their associated +costates. This capability is used for trait-based dispatch in augmented integration. + +Common use cases include: +- Hamiltonian systems: computing the costate of an augmented variable (โˆ‚H/โˆ‚v integration) +- Optimal control: integrating control variables with their adjoint equations +- Parameter estimation: treating parameters as dynamic variables with derivatives + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> SupportsVariableCostate() isa Traits.AbstractVariableCostateCapability +true + +julia> NoVariableCostate() isa Traits.AbstractVariableCostateCapability +true +\`\`\` + +# Notes +- `SupportsVariableCostate` indicates the system can compute derivatives with respect to augmented variables +- `NoVariableCostate` indicates the system cannot compute such derivatives +- This trait is used for dispatch to determine whether augmented integration is possible +- The specific operations enabled depend on the system type and context + +See also: [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +abstract type AbstractVariableCostateCapability <: AbstractTrait end + +""" +$(TYPEDEF) + +Trait for systems/flows that support augmented variable integration. + +Indicates that the system or flow can compute derivatives with respect to augmented +variables (e.g., parameters, controls) and integrate their associated costates. +This typically requires automatic differentiation support and variable dependence. + +Common use cases include: +- Hamiltonian systems: computing โˆ‚H/โˆ‚v via AD from a scalar Hamiltonian +- Optimal control: integrating control variables with their adjoint equations +- General systems: any system where augmented variables need derivative computation + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> svc = SupportsVariableCostate() +SupportsVariableCostate() + +julia> svc isa Traits.AbstractVariableCostateCapability +true +\`\`\` + +# Notes +- Used as a return value from `variable_costate_trait` for systems that support augmented variable integration +- Typically requires AD support and variable dependence in the system +- This trait enables augmented integration operations in flow calls +- The specific implementation depends on the system type and AD backend + +See also: [`CTFlows.Traits.AbstractVariableCostateCapability`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref), [`CTFlows.Traits.variable_costate_trait`](@ref). +""" +struct SupportsVariableCostate <: AbstractVariableCostateCapability end + +""" +$(TYPEDEF) + +Trait for systems/flows that do not support augmented variable integration. + +Indicates that the system or flow cannot compute derivatives with respect to augmented +variables or integrate their associated costates. This is the default for most systems, +including those with pre-computed derivatives or fixed parameters. + +Common use cases include: +- Hamiltonian systems: pre-computed Hamiltonian vector fields without AD +- Fixed systems: systems with parameters that are not treated as dynamic variables +- General systems: any system where augmented variable integration is not applicable + +# Example +\`\`\`julia-repl +julia> using CTFlows.Traits + +julia> nvc = NoVariableCostate() +NoVariableCostate() + +julia> nvc isa Traits.AbstractVariableCostateCapability +true +\`\`\` + +# Notes +- Default return value from `variable_costate_trait` for most systems and flows +- Systems without AD support or with fixed variable dependence typically return this +- Attempting augmented integration operations on such systems will throw an error +- The specific constraints depend on the system type + +See also: [`CTFlows.Traits.AbstractVariableCostateCapability`](@ref), [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.variable_costate_trait`](@ref). +""" +struct NoVariableCostate <: AbstractVariableCostateCapability end + +""" +$(TYPEDSIGNATURES) + +Return the augmented variable integration capability trait of a system or flow. + +# Arguments +- `obj`: Any object (default implementation returns `NoVariableCostate`). + +# Returns +- `Type{<:AbstractVariableCostateCapability}`: The capability trait, either + `SupportsVariableCostate` or `NoVariableCostate`. + +# Notes +- Default implementation returns `NoVariableCostate` for all objects +- Specialized implementations on system and flow types return the appropriate trait based on the system's capabilities +- Used for dispatch to determine if augmented integration operations are possible +- The specific operations enabled depend on the system type + +See also: [`CTFlows.Traits.SupportsVariableCostate`](@ref), [`CTFlows.Traits.NoVariableCostate`](@ref). +""" +variable_costate_trait(::Any) = NoVariableCostate diff --git a/src/Traits/variable_dependence.jl b/src/Traits/variable_dependence.jl new file mode 100644 index 00000000..edafc2df --- /dev/null +++ b/src/Traits/variable_dependence.jl @@ -0,0 +1,196 @@ +""" +$(TYPEDEF) + +Abstract supertype for variable-dependence traits. + +# Trait Pattern + +Objects that have a variable-dependence trait must implement two methods: +- `has_variable_dependence_trait(obj::MyType) = true`: Indicates the type has this trait +- `variable_dependence(obj::MyType)`: Returns the specific trait value (`Fixed` or `NonFixed`) + +Once these are implemented, the object automatically gains: +- `is_variable(obj)`: Returns true if `variable_dependence(obj)` is `NonFixed` +- `is_nonvariable(obj)`: Returns true if `variable_dependence(obj)` is `Fixed` +- `has_variable(obj)`: Alias for `is_variable` (CTModels compatibility) + +If `has_variable_dependence_trait` is not implemented or returns `false`, +calling `is_variable`, `is_nonvariable`, `has_variable`, or `variable_dependence` will throw an error +indicating the object does not support variable-dependence queries. +""" +abstract type VariableDependence <: AbstractTrait end + +""" +$(TYPEDEF) + +Trait indicating the system or function has no variable parameters. + +Indicates that the system operates with fixed parameters only, without additional +variable arguments that can be treated as dynamic variables during integration. + +Common use cases include: +- Functions with fixed parameters only +- Systems with constant parameters that are not integrated +- Configurations where all parameters are known at compile time + +See also: [`CTFlows.Traits.NonFixed`](@ref), [`CTFlows.Traits.VariableDependence`](@ref). +""" +struct Fixed <: VariableDependence end + +""" +$(TYPEDEF) + +Trait indicating the system or function depends on variable parameters. + +Indicates that the system operates with additional variable parameters that can +be treated as dynamic variables during integration or optimization. These variables +may be integrated alongside state variables or used for sensitivity analysis. + +Common use cases include: +- Functions with an extra variable argument `v` +- Systems with parameters that vary during integration +- Configurations where parameters are treated as control variables +- Sensitivity analysis and parameter estimation + +See also: [`CTFlows.Traits.Fixed`](@ref), [`CTFlows.Traits.VariableDependence`](@ref). +""" +struct NonFixed <: VariableDependence end + +# ============================================================================= +# Check has trait +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Check if the object has the variable-dependence trait. + +This fallback method throws an error indicating the object does not support +variable-dependence queries. Concrete types that have the trait should implement +`has_variable_dependence_trait(obj::MyType) = true`. + +The calling function name is automatically detected from the stacktrace +for better error messages. + +# Arguments +- `obj::Any`: The object to check. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): Always, indicating the object does not have the trait. + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.variable_dependence`](@ref). +""" +function has_variable_dependence_trait(obj::Any) + source_method = _caller_function_name() + throw(Exceptions.IncorrectArgument( + "Cannot call $(source_method) on object of type $(typeof(obj)): no variable-dependence trait"; + suggestion = "Implement has_variable_dependence_trait(obj::$(typeof(obj))) = true and variable_dependence(obj::$(typeof(obj))) to enable variable-dependence trait support.", + context = "Variable-dependence trait not available", + )) +end + +""" +$(TYPEDSIGNATURES) + +Return the variable-dependence trait value for the object. + +This fallback method throws an error indicating the method is not implemented. +Concrete types that have the trait should implement `variable_dependence(obj::MyType)` +to return the specific trait value (`Fixed` or `NonFixed`). + +# Arguments +- `obj::Any`: The object to query. + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): Always, indicating the method must be implemented. + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.has_variable_dependence_trait`](@ref). +""" +function variable_dependence(obj::Any) + has_variable_dependence_trait(obj) + throw(Exceptions.NotImplemented( + "variable_dependence not implemented for $(typeof(obj))"; + required_method = "variable_dependence(obj::$(typeof(obj)))", + suggestion = "Implement variable_dependence for your concrete object type to return the specific variable-dependence trait (Fixed or NonFixed).", + context = "Variable-dependence trait - required method implementation", + )) +end + +# ============================================================================= +# Trait accessors +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return true if the object depends on variable parameters. + +Checks that the object has the variable-dependence trait, then returns true +if `variable_dependence(obj)` is `NonFixed`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object depends on variable parameters. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support variable-dependence queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `variable_dependence` is not implemented for the object type. + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.variable_dependence`](@ref). +""" +function OCP.is_variable(obj::Any) + has_variable_dependence_trait(obj) + return variable_dependence(obj) === NonFixed +end + +""" +$(TYPEDSIGNATURES) + +Return true if the object does not depend on variable parameters. + +Checks that the object has the variable-dependence trait, then returns true +if `variable_dependence(obj)` is `Fixed`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object does not depend on variable parameters. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support variable-dependence queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `variable_dependence` is not implemented for the object type. + +See also: [`CTFlows.Traits.VariableDependence`](@ref), [`CTFlows.Traits.variable_dependence`](@ref). +""" +function OCP.is_nonvariable(obj::Any) + has_variable_dependence_trait(obj) + return variable_dependence(obj) === Fixed +end + +""" +$(TYPEDSIGNATURES) + +Return true if the object depends on variable parameters. + +Checks that the object has the variable-dependence trait, then returns true +if `variable_dependence(obj)` is `NonFixed`. + +# Arguments +- `obj::Any`: The object to check. + +# Returns +- `Bool`: true if the object depends on variable parameters. + +# Throws +- [`CTBase.Exceptions.IncorrectArgument`](@extref): If the object does not support variable-dependence queries. +- [`CTBase.Exceptions.NotImplemented`](@extref): If `variable_dependence` is not implemented for the object type. + +See also: [`CTFlows.Traits.is_variable`](@ref), [`CTFlows.Traits.VariableDependence`](@ref). +""" +function OCP.has_variable(obj::Any) + has_variable_dependence_trait(obj) + return variable_dependence(obj) === NonFixed +end diff --git a/test/coverage.jl b/test/coverage.jl new file mode 100644 index 00000000..67780ce0 --- /dev/null +++ b/test/coverage.jl @@ -0,0 +1,33 @@ +# ============================================================================== +# CTFlows Coverage Post-Processing +# ============================================================================== +# +# This script post-processes coverage data generated by Julia's code coverage +# system to produce human-readable reports and CI-compatible artifacts. +# +# Prerequisites: +# - The Coverage package must be installed in your base Julia environment: +# julia --project=@v1.12 -e 'using Pkg; Pkg.add("Coverage")' +# +# Usage: +# julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; coverage=true); include("test/coverage.jl")' +# +# Artifacts generated: +# - coverage/lcov.info: LCOV format for CI integration (e.g., Codecov) +# - coverage/cov_report.md: Human-readable summary with uncovered lines +# - coverage/cov/: Archived .cov files +# +# ============================================================================== +pushfirst!(LOAD_PATH, @__DIR__) +using Coverage +using CTBase + +const WORST_N_FILES = 50 +const MAX_UNCOVERED_LINES = 500 + +CTBase.postprocess_coverage(; + root_dir=dirname(@__DIR__), + dest_dir="coverage", + worst_n_files=WORST_N_FILES, + max_uncovered_lines=MAX_UNCOVERED_LINES, +) \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 700a1931..afc9be61 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,35 +1,61 @@ -using Aqua +# ============================================================================== +# CTFlows Test Runner +# ============================================================================== +# +# ## Running tests +# +# ### All tests +# julia --project -e 'using Pkg; Pkg.test("CTFlows")' +# +# ### Specific test(s) โ€” glob patterns matched against test file paths/names +# julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["test_abstract_system"])' +# julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["*pipelines*"])' +# julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["-n"])' # dry run +# +# Test layout: `suite//test_.jl` each defining `test_()`. +# ============================================================================== using Test -using CTFlows -using OrdinaryDiffEq using CTBase -using Plots -using LinearAlgebra -using CTModels -import CTParser: CTParser, @def - -const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) # to test functions from CTFlowsODE not in CTFlows - -@testset verbose = true showtiming = true "CTFlows" begin - for name in [ - :types, - :differential_geometry, - :aqua, - :concatenation, - :default, - :flow_function, - :flow_hamiltonian_vector_field, - :flow_hamiltonian, - :flow_vector_field, - :optimal_control_problem, - :augmented_flow, - ] - @testset "$(name)" begin - test_name = Symbol(:test_, name) - println("testing: ", string(name)) - include("$(test_name).jl") - @eval $test_name() - end - end +using CTFlows + +# Trigger loading of optional extensions +const TestRunner = Base.get_extension(CTBase, :TestRunner) + +# Controls nested testset output formatting (used by individual test files) +module TestOptions +const VERBOSE = true +const SHOWTIMING = true +end + +using .TestOptions: VERBOSE, SHOWTIMING + +# Run tests using the TestRunner extension +CTBase.run_tests(; + args=String.(ARGS), + testset_name="CTFlows tests", + available_tests=("suite/*/test_*",), + filename_builder=name -> Symbol(:test_, name), + funcname_builder=name -> Symbol(:test_, name), + verbose=VERBOSE, + showtiming=SHOWTIMING, + test_dir=@__DIR__, +) + +# If running with coverage enabled, remind the user to run the post-processing script +# because .cov files are flushed at process exit and cannot be cleaned up by this script. +if Base.JLOptions().code_coverage != 0 + println( + """ + +================================================================================ +[CTFlows] Coverage files generated. + +To process them, move them to the coverage/ directory, and generate a report, +please run: + + julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; coverage=true); include("test/coverage.jl")' +================================================================================ +""", + ) end diff --git a/test/suite/common/test_abstract_tag.jl b/test/suite/common/test_abstract_tag.jl new file mode 100644 index 00000000..ac77a0be --- /dev/null +++ b/test/suite/common/test_abstract_tag.jl @@ -0,0 +1,34 @@ +module TestAbstractTag + +import Test +import CTFlows.Common + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_tag() + Test.@testset "Abstract Tag Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type + # ==================================================================== + + Test.@testset "Abstract Type" begin + Test.@testset "AbstractTag is exported" begin + Test.@test isdefined(Common, :AbstractTag) + end + + Test.@testset "AbstractTag is abstract" begin + Test.@test isabstracttype(Common.AbstractTag) + end + end + end +end + +end # module + +test_abstract_tag() = TestAbstractTag.test_abstract_tag() diff --git a/test/suite/common/test_common_module.jl b/test/suite/common/test_common_module.jl new file mode 100644 index 00000000..c455e1d3 --- /dev/null +++ b/test/suite/common/test_common_module.jl @@ -0,0 +1,197 @@ +""" +# ============================================================================ +# Common Module Exports Tests +# ============================================================================ +# This file tests the exports from the `Common` module. It verifies that +# the expected types, functions, and constants are properly exported by +# `CTFlows.Common` and readily accessible to the end user. +""" + +module TestCommonModule + +import Test +import CTFlows.Common +import CTFlows.Traits +import CTModels.OCP + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCommonModule + +function test_common_module() + Test.@testset "Common Module Exports" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # Tag Types + # ==================================================================== + + Test.@testset "Tag Types" begin + Test.@testset "AbstractTag is exported" begin + Test.@test isdefined(Common, :AbstractTag) + Test.@test isabstracttype(Common.AbstractTag) + end + end + + # ==================================================================== + # Config Types (now in Configs module) + # ==================================================================== + + # Config types have been moved to CTFlows.Configs module + # See test/suite/configs/test_configs.jl for config tests + + # ==================================================================== + # Trait Types (now in Traits module) + # ==================================================================== + + Test.@testset "Trait Types (Traits module)" begin + Test.@testset "TimeDependence is exported from Traits" begin + Test.@test isdefined(Traits, :TimeDependence) + Test.@test isabstracttype(Traits.TimeDependence) + end + + Test.@testset "Autonomous is exported from Traits" begin + Test.@test isdefined(OCP, :Autonomous) + Test.@test Traits.Autonomous <: Traits.TimeDependence + end + + Test.@testset "NonAutonomous is exported from Traits" begin + Test.@test isdefined(OCP, :NonAutonomous) + Test.@test Traits.NonAutonomous <: Traits.TimeDependence + end + end + + Test.@testset "Variable-Dependence Trait Types (Traits module)" begin + Test.@testset "VariableDependence is exported from Traits" begin + Test.@test isdefined(Traits, :VariableDependence) + Test.@test isabstracttype(Traits.VariableDependence) + end + + Test.@testset "Fixed is exported from Traits" begin + Test.@test isdefined(Traits, :Fixed) + Test.@test Traits.Fixed <: Traits.VariableDependence + trait = Traits.Fixed() + Test.@test trait isa Traits.Fixed + end + + Test.@testset "NonFixed is exported from Traits" begin + Test.@test isdefined(Traits, :NonFixed) + Test.@test Traits.NonFixed <: Traits.VariableDependence + trait = Traits.NonFixed() + Test.@test trait isa Traits.NonFixed + end + end + + # ==================================================================== + # ODEParameters Type + # ==================================================================== + + Test.@testset "ODEParameters Type" begin + Test.@testset "ODEParameters is exported" begin + Test.@test isdefined(Common, :ODEParameters) + end + + Test.@testset "constructs with nothing" begin + params = Common.ODEParameters(nothing) + Test.@test params isa Common.ODEParameters + Test.@test Common.variable(params) === nothing + end + + Test.@testset "constructs with value" begin + params = Common.ODEParameters(0.5) + Test.@test params isa Common.ODEParameters + Test.@test Common.variable(params) == 0.5 + end + end + + # ==================================================================== + # Trait Check Functions (now in Traits module) + # ==================================================================== + + Test.@testset "Trait Check Functions (Traits module)" begin + Test.@testset "has_time_dependence_trait is exported from Traits" begin + Test.@test isdefined(Traits, :has_time_dependence_trait) + end + + Test.@testset "has_variable_dependence_trait is exported from Traits" begin + Test.@test isdefined(Traits, :has_variable_dependence_trait) + end + end + + # ==================================================================== + # Trait Query Functions (now in Traits module) + # ==================================================================== + + Test.@testset "Trait Query Functions (Traits module)" begin + Test.@testset "time_dependence is exported from Traits" begin + Test.@test isdefined(Traits, :time_dependence) + end + + Test.@testset "variable_dependence is exported from Traits" begin + Test.@test isdefined(Traits, :variable_dependence) + end + end + + # ==================================================================== + # Trait Accessor Functions (now in Traits module) + # ==================================================================== + + Test.@testset "Trait Accessor Functions (Traits module)" begin + Test.@testset "is_autonomous is exported from Traits" begin + Test.@test isdefined(Traits, :is_autonomous) + end + + Test.@testset "is_nonautonomous is exported from Traits" begin + Test.@test isdefined(Traits, :is_nonautonomous) + end + + Test.@testset "is_variable is exported from Traits" begin + Test.@test isdefined(Traits, :is_variable) + end + + Test.@testset "is_nonvariable is exported from Traits" begin + Test.@test isdefined(Traits, :is_nonvariable) + end + + Test.@testset "has_variable is exported from Traits" begin + Test.@test isdefined(Traits, :has_variable) + end + end + + # ==================================================================== + # Type Hierarchy Verification (Traits module) + # ==================================================================== + + Test.@testset "Type Hierarchy (Traits module)" begin + Test.@testset "TimeDependence hierarchy" begin + Test.@test Traits.Autonomous <: Traits.TimeDependence + Test.@test Traits.NonAutonomous <: Traits.TimeDependence + end + + Test.@testset "VariableDependence hierarchy" begin + Test.@test Traits.Fixed <: Traits.VariableDependence + Test.@test Traits.NonFixed <: Traits.VariableDependence + end + end + + # ==================================================================== + # Internal Norm Functions + # ==================================================================== + + Test.@testset "Internal Norm Functions" begin + Test.@testset "deepvalue is exported" begin + Test.@test isdefined(Common, :deepvalue) + Test.@test Common.deepvalue(3.14) === 3.14 + end + + Test.@testset "real_norm is exported" begin + Test.@test isdefined(Common, :real_norm) + Test.@test Common.real_norm(3.0, 0.0) === 3.0 + end + end + end +end + +end # module + +test_common_module() = TestCommonModule.test_common_module() diff --git a/test/suite/common/test_default.jl b/test/suite/common/test_default.jl new file mode 100644 index 00000000..e753ef1a --- /dev/null +++ b/test/suite/common/test_default.jl @@ -0,0 +1,66 @@ +module TestDefault + +import Test +import CTFlows.Common + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_default() + Test.@testset "Default Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Default Value Functions + # ==================================================================== + + Test.@testset "Default Value Functions" begin + Test.@testset "__is_autonomous returns true" begin + Test.@test Common.__is_autonomous() === true + end + + Test.@testset "__is_variable returns false" begin + Test.@test Common.__is_variable() === false + end + + Test.@testset "__variable returns NotProvided" begin + Test.@test Common.__variable() isa Common.NotProvided + end + + Test.@testset "__unsafe returns false" begin + Test.@test Common.__unsafe() === false + end + + Test.@testset "__is_inplace returns nothing" begin + Test.@test Common.__is_inplace() === nothing + end + + Test.@testset "_variable_costate returns false" begin + Test.@test Common._variable_costate() === false + end + end + + Test.@testset "NotProvided type" begin + Test.@testset "NotProvided is a concrete type" begin + Test.@test Common.NotProvided <: Any + end + + Test.@testset "NotProvided is exported" begin + Test.@test isdefined(Common, :NotProvided) + end + end + + Test.@testset "_variable_costate export" begin + Test.@testset "_variable_costate is exported" begin + Test.@test isdefined(Common, :_variable_costate) + end + end + end +end + +end # module + +test_default() = TestDefault.test_default() diff --git a/test/suite/common/test_internal_norm.jl b/test/suite/common/test_internal_norm.jl new file mode 100644 index 00000000..91f382b2 --- /dev/null +++ b/test/suite/common/test_internal_norm.jl @@ -0,0 +1,43 @@ +module TestInternalNorm + +import Test +import CTFlows.Common: Common + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_internal_norm() + Test.@testset "Internal Norm Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - deepvalue + # ==================================================================== + + Test.@testset "deepvalue" begin + Test.@testset "deepvalue(x::Real) is identity" begin + Test.@test Common.deepvalue(1.0) === 1.0 + Test.@test Common.deepvalue(2.5) === 2.5 + Test.@test Common.deepvalue(-3.0) === -3.0 + Test.@test Common.deepvalue(0.0) === 0.0 + end + end + + # ==================================================================== + # UNIT TESTS - real_norm + # ==================================================================== + + Test.@testset "real_norm" begin + Test.@testset "real_norm(u::Real, t) is abs" begin + Test.@test Common.real_norm(3.0, 0.0) === 3.0 + Test.@test Common.real_norm(-5.0, 0.0) === 5.0 + Test.@test Common.real_norm(0.0, 0.0) === 0.0 + Test.@test Common.real_norm(2.5, 1.0) === 2.5 + end + end + + end +end + +end # module + +test_internal_norm() = TestInternalNorm.test_internal_norm() \ No newline at end of file diff --git a/test/suite/common/test_ode_parameters.jl b/test/suite/common/test_ode_parameters.jl new file mode 100644 index 00000000..5de50dbc --- /dev/null +++ b/test/suite/common/test_ode_parameters.jl @@ -0,0 +1,123 @@ +module TestODEParameters + +import Test +import CTFlows.Common + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake type for cache testing +# ============================================================================== + +struct FakeCache <: Common.AbstractCache end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_ode_parameters() + Test.@testset "ODEParameters Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Constructor + # ==================================================================== + + Test.@testset "Constructor" begin + + Test.@testset "accepts nothing for Fixed systems" begin + params = Common.ODEParameters(nothing) + Test.@test Common.variable(params) === nothing + end + + Test.@testset "accepts value for NonFixed systems" begin + params = Common.ODEParameters(0.5) + Test.@test Common.variable(params) == 0.5 + end + + Test.@testset "accepts vector for NonFixed systems" begin + params = Common.ODEParameters([1.0, 2.0]) + Test.@test Common.variable(params) == [1.0, 2.0] + end + end + + # ==================================================================== + # UNIT TESTS - Accessor function + # ==================================================================== + + Test.@testset "Accessor function" begin + Test.@testset "variable accessor returns nothing" begin + params = Common.ODEParameters(nothing) + Test.@test Common.variable(params) === nothing + end + + Test.@testset "variable accessor returns value" begin + params = Common.ODEParameters(0.5) + Test.@test Common.variable(params) == 0.5 + end + + Test.@testset "variable accessor returns vector" begin + params = Common.ODEParameters([1.0, 2.0]) + Test.@test Common.variable(params) == [1.0, 2.0] + end + end + + # ==================================================================== + # UNIT TESTS - Type parameters + # ==================================================================== + + Test.@testset "Type parameters" begin + + Test.@testset "infers Nothing type" begin + params = Common.ODEParameters(nothing) + Test.@test params isa Common.ODEParameters{Nothing} + end + + Test.@testset "infers Float64 type" begin + params = Common.ODEParameters(0.5) + Test.@test params isa Common.ODEParameters{Float64} + end + + Test.@testset "infers Vector{Float64} type" begin + params = Common.ODEParameters([1.0, 2.0]) + Test.@test params isa Common.ODEParameters{Vector{Float64}} + end + + # ==================================================================== + # UNIT TESTS - Cache field + # ==================================================================== + + Test.@testset "Cache field" begin + + Test.@testset "Backward compat: 1-arg constructor gives cache=nothing" begin + params = Common.ODEParameters(nothing) + Test.@test Common.cache(params) === nothing + end + + Test.@testset "2-arg constructor with cache" begin + fake = FakeCache() + params = Common.ODEParameters(0.5, fake) + Test.@test Common.cache(params) isa FakeCache + Test.@test Common.cache(params) === fake + end + + Test.@testset "Type parameters with cache" begin + params = Common.ODEParameters(nothing) + Test.@test params isa Common.ODEParameters{Nothing, Nothing} + fake = FakeCache() + params2 = Common.ODEParameters(0.5, fake) + Test.@test params2 isa Common.ODEParameters{Float64, FakeCache} + end + + Test.@testset "Type stability: cache accessor" begin + params = Common.ODEParameters(nothing) + Test.@test Test.@inferred Common.cache(params) === nothing + end + end + end + end +end + +end # module + +test_ode_parameters() = TestODEParameters.test_ode_parameters() diff --git a/test/suite/configs/test_abstract_configs.jl b/test/suite/configs/test_abstract_configs.jl new file mode 100644 index 00000000..cdf81639 --- /dev/null +++ b/test/suite/configs/test_abstract_configs.jl @@ -0,0 +1,144 @@ +module TestAbstractConfigs + +import Test +import CTFlows.Configs +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake config type for testing the AbstractConfig contract. +""" +struct FakeConfig{X0} <: Configs.AbstractConfigWithMaC{X0, Traits.PointTrait, Traits.StateTrait} + x0::X0 +end + +function test_abstract_configs() + Test.@testset "Abstract Configs Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractConfig" begin + Test.@testset "AbstractConfig is exported" begin + Test.@test isdefined(Configs, :AbstractConfig) + end + + Test.@testset "AbstractConfig is abstract" begin + Test.@test isabstracttype(Configs.AbstractConfig) + end + end + + Test.@testset "AbstractConfigWithMaC" begin + Test.@testset "AbstractConfigWithMaC is exported" begin + Test.@test isdefined(Configs, :AbstractConfigWithMaC) + end + + Test.@testset "AbstractConfigWithMaC is abstract" begin + Test.@test isabstracttype(Configs.AbstractConfigWithMaC) + end + end + + Test.@testset "Type Aliases" begin + Test.@testset "AbstractPointConfig is exported" begin + Test.@test isdefined(Configs, :AbstractPointConfig) + end + + Test.@testset "AbstractTrajectoryConfig is exported" begin + Test.@test isdefined(Configs, :AbstractTrajectoryConfig) + end + + Test.@testset "AbstractStateConfig is exported" begin + Test.@test isdefined(Configs, :AbstractStateConfig) + end + + Test.@testset "AbstractHamiltonianConfig is exported" begin + Test.@test isdefined(Configs, :AbstractHamiltonianConfig) + end + + Test.@testset "AbstractAugmentedHamiltonianConfig is exported" begin + Test.@test isdefined(Configs, :AbstractAugmentedHamiltonianConfig) + end + end + + Test.@testset "Trait Alias Hierarchy" begin + Test.@testset "StatePointConfig subtypes" begin + Test.@test Configs.StatePointConfig <: Configs.AbstractPointConfig + Test.@test Configs.StatePointConfig <: Configs.AbstractStateConfig + end + + Test.@testset "HamiltonianPointConfig subtypes" begin + Test.@test Configs.HamiltonianPointConfig <: Configs.AbstractPointConfig + Test.@test Configs.HamiltonianPointConfig <: Configs.AbstractHamiltonianConfig + end + + Test.@testset "StateTrajectoryConfig subtypes" begin + Test.@test Configs.StateTrajectoryConfig <: Configs.AbstractTrajectoryConfig + Test.@test Configs.StateTrajectoryConfig <: Configs.AbstractStateConfig + end + + Test.@testset "HamiltonianTrajectoryConfig subtypes" begin + Test.@test Configs.HamiltonianTrajectoryConfig <: Configs.AbstractTrajectoryConfig + Test.@test Configs.HamiltonianTrajectoryConfig <: Configs.AbstractHamiltonianConfig + end + + Test.@testset "Negative checks" begin + Test.@test !(Configs.StatePointConfig <: Configs.AbstractHamiltonianConfig) + Test.@test !(Configs.HamiltonianPointConfig <: Configs.AbstractStateConfig) + Test.@test !(Configs.StatePointConfig <: Configs.AbstractTrajectoryConfig) + end + end + end + + # ==================================================================== + # UNIT TESTS - Trait Accessors + # ==================================================================== + + Test.@testset "UNIT TESTS - Trait Accessors" begin + Test.@testset "mode_trait" begin + Test.@testset "mode_trait for StatePointConfig" begin + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + Test.@test Configs.mode_trait(config) === Traits.PointTrait + end + + Test.@testset "mode_trait for StateTrajectoryConfig" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0]) + Test.@test Configs.mode_trait(config) === Traits.TrajectoryTrait + end + + Test.@testset "mode_trait for HamiltonianPointConfig" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) + Test.@test Configs.mode_trait(config) === Traits.PointTrait + end + + Test.@testset "mode_trait for HamiltonianTrajectoryConfig" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0], [0.5]) + Test.@test Configs.mode_trait(config) === Traits.TrajectoryTrait + end + end + + Test.@testset "content_trait" begin + Test.@testset "content_trait for StatePointConfig" begin + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + Test.@test Configs.content_trait(config) === Traits.StateTrait + end + + Test.@testset "content_trait for HamiltonianPointConfig" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) + Test.@test Configs.content_trait(config) === Traits.HamiltonianTrait + end + end + end + end +end + +end # module + +test_abstract_configs() = TestAbstractConfigs.test_abstract_configs() diff --git a/test/suite/configs/test_concrete_configs.jl b/test/suite/configs/test_concrete_configs.jl new file mode 100644 index 00000000..a621359b --- /dev/null +++ b/test/suite/configs/test_concrete_configs.jl @@ -0,0 +1,166 @@ +module TestConcreteConfigs + +import Test +import CTFlows.Configs +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_concrete_configs() + Test.@testset "Concrete Configs Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Concrete Type Construction + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Type Construction" begin + Test.@testset "StatePointConfig construction" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + Test.@test config isa Configs.StatePointConfig + Test.@test config.t0 === 0.0 + Test.@test config.x0 == [1.0, 0.0] + Test.@test config.tf === 1.0 + end + + Test.@testset "StateTrajectoryConfig construction" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + Test.@test config isa Configs.StateTrajectoryConfig + Test.@test config.tspan == (0.0, 1.0) + Test.@test config.x0 == [1.0, 0.0] + end + + Test.@testset "HamiltonianPointConfig construction" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + Test.@test config isa Configs.HamiltonianPointConfig + Test.@test config.t0 === 0.0 + Test.@test config.x0 == [1.0, 0.0] + Test.@test config.p0 == [0.5, 0.3] + Test.@test config.tf === 1.0 + end + + Test.@testset "HamiltonianTrajectoryConfig construction" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 0.0], [0.5, 0.3]) + Test.@test config isa Configs.HamiltonianTrajectoryConfig + Test.@test config.tspan == (0.0, 1.0) + Test.@test config.x0 == [1.0, 0.0] + Test.@test config.p0 == [0.5, 0.3] + end + + Test.@testset "AugmentedHamiltonianPointConfig construction" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + Test.@test config isa Configs.AugmentedHamiltonianPointConfig + Test.@test config.t0 === 0.0 + Test.@test config.x0 == [1.0, 0.0] + Test.@test config.p0 == [0.5, 0.3] + Test.@test config.pv0 == [0.0, 0.0] + Test.@test config.tf === 1.0 + end + end + + # ==================================================================== + # UNIT TESTS - Concrete Type Subtype Relationships + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Type Subtype Relationships" begin + Test.@testset "StatePointConfig subtypes" begin + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + Test.@test config isa Configs.AbstractConfig + Test.@test config isa Configs.AbstractPointConfig + Test.@test config isa Configs.AbstractStateConfig + Test.@test Configs.StatePointConfig <: Configs.AbstractConfig + Test.@test Configs.StatePointConfig <: Configs.AbstractPointConfig + Test.@test Configs.StatePointConfig <: Configs.AbstractStateConfig + end + + Test.@testset "StateTrajectoryConfig subtypes" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0]) + Test.@test config isa Configs.AbstractConfig + Test.@test config isa Configs.AbstractTrajectoryConfig + Test.@test config isa Configs.AbstractStateConfig + Test.@test Configs.StateTrajectoryConfig <: Configs.AbstractConfig + Test.@test Configs.StateTrajectoryConfig <: Configs.AbstractTrajectoryConfig + Test.@test Configs.StateTrajectoryConfig <: Configs.AbstractStateConfig + end + + Test.@testset "HamiltonianPointConfig subtypes" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) + Test.@test config isa Configs.AbstractConfig + Test.@test config isa Configs.AbstractPointConfig + Test.@test config isa Configs.AbstractHamiltonianConfig + Test.@test Configs.HamiltonianPointConfig <: Configs.AbstractConfig + Test.@test Configs.HamiltonianPointConfig <: Configs.AbstractPointConfig + Test.@test Configs.HamiltonianPointConfig <: Configs.AbstractHamiltonianConfig + end + + Test.@testset "HamiltonianTrajectoryConfig subtypes" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0], [0.5]) + Test.@test config isa Configs.AbstractConfig + Test.@test config isa Configs.AbstractTrajectoryConfig + Test.@test config isa Configs.AbstractHamiltonianConfig + Test.@test Configs.HamiltonianTrajectoryConfig <: Configs.AbstractConfig + Test.@test Configs.HamiltonianTrajectoryConfig <: Configs.AbstractTrajectoryConfig + Test.@test Configs.HamiltonianTrajectoryConfig <: Configs.AbstractHamiltonianConfig + end + + Test.@testset "AugmentedHamiltonianPointConfig subtypes" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0], [0.5], [0.0], 1.0) + Test.@test config isa Configs.AbstractAugmentedHamiltonianConfig + Test.@test config isa Configs.AbstractPointConfig + Test.@test Configs.AugmentedHamiltonianPointConfig <: Configs.AbstractAugmentedHamiltonianConfig + Test.@test Configs.AugmentedHamiltonianPointConfig <: Configs.AbstractPointConfig + end + end + + # ==================================================================== + # UNIT TESTS - AugmentedHamiltonianPointConfig Specific Methods + # ==================================================================== + + Test.@testset "UNIT TESTS - AugmentedHamiltonianPointConfig Specific Methods" begin + Test.@testset "AugmentedHamiltonianPointConfig initial_condition" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + ic = Configs.initial_condition(config) + Test.@test ic == [1.0, 0.0, 0.5, 0.3, 0.0, 0.0] + end + + Test.@testset "AugmentedHamiltonianPointConfig initial_state" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + Test.@test Configs.initial_state(config) == [1.0, 0.0] + end + + Test.@testset "AugmentedHamiltonianPointConfig initial_costate" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + Test.@test Configs.initial_costate(config) == [0.5, 0.3] + end + + Test.@testset "AugmentedHamiltonianPointConfig initial_variable_costate" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + Test.@test Configs.initial_variable_costate(config) == [0.0, 0.0] + end + + Test.@testset "AugmentedHamiltonianPointConfig tspan" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + Test.@test Configs.tspan(config) == (0.0, 1.0) + end + end + + # ==================================================================== + # TYPE STABILITY TESTS + # ==================================================================== + + Test.@testset "TYPE STABILITY TESTS" begin + Test.@testset "Type Stability: AugmentedHamiltonianPointConfig getters" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], [0.0, 0.0], 1.0) + Test.@test_nowarn Test.@inferred(Configs.initial_condition(config)) == [1.0, 0.0, 0.5, 0.3, 0.0, 0.0] + Test.@test_nowarn Test.@inferred(Configs.initial_state(config)) == [1.0, 0.0] + Test.@test_nowarn Test.@inferred(Configs.initial_costate(config)) == [0.5, 0.3] + Test.@test_nowarn Test.@inferred(Configs.initial_variable_costate(config)) == [0.0, 0.0] + Test.@test_nowarn Test.@inferred(Configs.tspan(config)) == (0.0, 1.0) + end + end + end +end + +end # module + +test_concrete_configs() = TestConcreteConfigs.test_concrete_configs() diff --git a/test/suite/configs/test_configs_module.jl b/test/suite/configs/test_configs_module.jl new file mode 100644 index 00000000..c60c4fe7 --- /dev/null +++ b/test/suite/configs/test_configs_module.jl @@ -0,0 +1,63 @@ +module TestConfigsModule + +import Test +import CTFlows.Configs +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_configs_module() + Test.@testset "Configs Module Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Module Structure + # ==================================================================== + + Test.@testset "UNIT TESTS - Module Structure" begin + Test.@testset "Configs module is defined" begin + Test.@test isdefined(Configs, :Configs) + end + + Test.@testset "Configs module has expected imports" begin + Test.@test isdefined(Configs, :Exceptions) + Test.@test isdefined(Configs, :Traits) + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported abstract types" begin + for sym in (:AbstractConfig, :AbstractConfigWithMaC, + :AbstractPointConfig, :AbstractTrajectoryConfig, + :AbstractStateConfig, :AbstractHamiltonianConfig, + :AbstractAugmentedHamiltonianConfig) + Test.@test isdefined(Configs, sym) + end + end + + Test.@testset "Exported concrete types" begin + for sym in (:StatePointConfig, :StateTrajectoryConfig, + :HamiltonianPointConfig, :HamiltonianTrajectoryConfig, + :AugmentedHamiltonianPointConfig) + Test.@test isdefined(Configs, sym) + end + end + + Test.@testset "Exported functions" begin + for sym in (:tspan, :initial_condition, :initial_state, :initial_costate, + :initial_variable_costate, :initial_time, :final_time, + :mode_trait, :content_trait) + Test.@test isdefined(Configs, sym) + end + end + end + end +end + +end # module + +test_configs_module() = TestConfigsModule.test_configs_module() diff --git a/test/suite/configs/test_implementations_configs.jl b/test/suite/configs/test_implementations_configs.jl new file mode 100644 index 00000000..fa8d7cc6 --- /dev/null +++ b/test/suite/configs/test_implementations_configs.jl @@ -0,0 +1,155 @@ +module TestImplementationsConfigs + +import Test +import CTBase.Exceptions +import CTFlows.Configs +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_implementations_configs() + Test.@testset "Implementations Configs Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - tspan Implementations + # ==================================================================== + + Test.@testset "UNIT TESTS - tspan Implementations" begin + Test.@testset "AbstractPointConfig tspan returns tuple" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + ts = Configs.tspan(config) + Test.@test ts isa Tuple{Real, Real} + Test.@test ts == (0.0, 1.0) + end + + Test.@testset "AbstractTrajectoryConfig tspan returns tuple" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + ts = Configs.tspan(config) + Test.@test ts isa Tuple{Real, Real} + Test.@test ts == (0.0, 1.0) + end + end + + # ==================================================================== + # UNIT TESTS - initial_time Implementations + # ==================================================================== + + Test.@testset "UNIT TESTS - initial_time Implementations" begin + Test.@testset "AbstractPointConfig initial_time returns t0" begin + config = Configs.StatePointConfig(0.5, [1.0], 1.0) + Test.@test Configs.initial_time(config) == 0.5 + end + + Test.@testset "AbstractTrajectoryConfig initial_time returns tspan[1]" begin + config = Configs.StateTrajectoryConfig((0.5, 2.5), [1.0]) + Test.@test Configs.initial_time(config) == 0.5 + end + end + + # ==================================================================== + # UNIT TESTS - final_time Implementations + # ==================================================================== + + Test.@testset "UNIT TESTS - final_time Implementations" begin + Test.@testset "AbstractPointConfig final_time returns tf" begin + config = Configs.StatePointConfig(0.0, [1.0], 2.5) + Test.@test Configs.final_time(config) == 2.5 + end + + Test.@testset "AbstractTrajectoryConfig final_time returns tspan[2]" begin + config = Configs.StateTrajectoryConfig((0.0, 2.5), [1.0]) + Test.@test Configs.final_time(config) == 2.5 + end + end + + # ==================================================================== + # UNIT TESTS - initial_condition Implementations + # ==================================================================== + + Test.@testset "UNIT TESTS - initial_condition Implementations" begin + Test.@testset "AbstractStateConfig with scalar X0 wraps in vector" begin + config = Configs.StatePointConfig(0.0, 1.0, 1.0) + ic = Configs.initial_condition(config) + Test.@test ic isa AbstractVector + Test.@test ic == [1.0] + end + + Test.@testset "AbstractStateConfig with vector X0 returns vector" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + ic = Configs.initial_condition(config) + Test.@test ic isa AbstractVector + Test.@test ic == [1.0, 0.0] + end + + Test.@testset "AbstractHamiltonianConfig returns vcat(x0, p0)" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + ic = Configs.initial_condition(config) + Test.@test ic == [1.0, 0.0, 0.5, 0.3] + end + end + + # ==================================================================== + # UNIT TESTS - initial_state Implementation + # ==================================================================== + + Test.@testset "UNIT TESTS - initial_state Implementation" begin + Test.@testset "initial_state returns x0 field" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + Test.@test Configs.initial_state(config) == [1.0, 0.0] + end + + Test.@testset "initial_state for HamiltonianPointConfig" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + Test.@test Configs.initial_state(config) == [1.0, 0.0] + end + + Test.@testset "initial_state for StateTrajectoryConfig" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + Test.@test Configs.initial_state(config) == [1.0, 0.0] + end + + Test.@testset "initial_state for HamiltonianTrajectoryConfig" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 0.0], [0.5, 0.3]) + Test.@test Configs.initial_state(config) == [1.0, 0.0] + end + end + + # ==================================================================== + # ERROR TESTS - initial_costate Implementations + # ==================================================================== + + Test.@testset "ERROR TESTS - initial_costate Implementations" begin + Test.@testset "initial_costate throws PreconditionError for state configs" begin + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + Test.@test_throws Exceptions.PreconditionError Configs.initial_costate(config) + end + + Test.@testset "PreconditionError message quality" begin + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + e = try + Configs.initial_costate(config) + catch err + err + end + Test.@test e isa Exceptions.PreconditionError + Test.@test occursin("Hamiltonian", string(e)) + Test.@test occursin("costate", string(e)) + end + + Test.@testset "initial_costate returns p0 for Hamiltonian configs" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + Test.@test Configs.initial_costate(config) == [0.5, 0.3] + end + + Test.@testset "initial_costate for HamiltonianTrajectoryConfig" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 0.0], [0.5, 0.3]) + Test.@test Configs.initial_costate(config) == [0.5, 0.3] + end + end + end +end + +end # module + +test_implementations_configs() = TestImplementationsConfigs.test_implementations_configs() diff --git a/test/suite/configs/test_interface_configs.jl b/test/suite/configs/test_interface_configs.jl new file mode 100644 index 00000000..91351a9e --- /dev/null +++ b/test/suite/configs/test_interface_configs.jl @@ -0,0 +1,86 @@ +module TestInterfaceConfigs + +import Test +import CTBase.Exceptions +import CTFlows.Configs +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake bare config that intentionally does not implement any AbstractConfig stubs. +""" +struct FakeBareConfig <: Configs.AbstractConfig{Float64} end + +function test_interface_configs() + Test.@testset "Interface Configs Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # ERROR TESTS - Interface Stubs + # ==================================================================== + + Test.@testset "ERROR TESTS - Interface Stubs" begin + Test.@testset "tspan stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.tspan(config) + end + + Test.@testset "initial_time stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.initial_time(config) + end + + Test.@testset "final_time stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.final_time(config) + end + + Test.@testset "initial_condition stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.initial_condition(config) + end + + Test.@testset "initial_state stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.initial_state(config) + end + + Test.@testset "initial_costate stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.initial_costate(config) + end + + Test.@testset "initial_variable_costate stub throws NotImplemented" begin + config = FakeBareConfig() + Test.@test_throws Exceptions.NotImplemented Configs.initial_variable_costate(config) + end + end + + # ==================================================================== + # ERROR TESTS - Exception Quality + # ==================================================================== + + Test.@testset "ERROR TESTS - Exception Quality" begin + Test.@testset "NotImplemented error message quality" begin + config = FakeBareConfig() + e = try + Configs.tspan(config) + catch err + err + end + Test.@test e isa Exceptions.NotImplemented + Test.@test occursin("tspan", string(e)) + Test.@test occursin("not implemented", lowercase(string(e))) + end + end + end +end + +end # module + +test_interface_configs() = TestInterfaceConfigs.test_interface_configs() diff --git a/test/suite/configs/test_show_configs.jl b/test/suite/configs/test_show_configs.jl new file mode 100644 index 00000000..4b315868 --- /dev/null +++ b/test/suite/configs/test_show_configs.jl @@ -0,0 +1,136 @@ +module TestShowConfigs + +import Test +import CTFlows.Configs + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_show_configs() + Test.@testset "Show Configs Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Display Methods + # ==================================================================== + + Test.@testset "UNIT TESTS - Display Methods" begin + Test.@testset "StatePointConfig show methods" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + io = IOBuffer() + show(io, config) + output = String(take!(io)) + Test.@test occursin("StatePointConfig", output) + Test.@test occursin("t0:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("tf:", output) + end + + Test.@testset "StatePointConfig text/plain show method" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + io = IOBuffer() + show(io, MIME("text/plain"), config) + output = String(take!(io)) + Test.@test occursin("StatePointConfig", output) + Test.@test occursin("t0:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("tf:", output) + end + + Test.@testset "StateTrajectoryConfig show methods" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + io = IOBuffer() + show(io, config) + output = String(take!(io)) + Test.@test occursin("StateTrajectoryConfig", output) + Test.@test occursin("tspan:", output) + Test.@test occursin("x0:", output) + end + + Test.@testset "StateTrajectoryConfig text/plain show method" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + io = IOBuffer() + show(io, MIME("text/plain"), config) + output = String(take!(io)) + Test.@test occursin("StateTrajectoryConfig", output) + Test.@test occursin("tspan:", output) + Test.@test occursin("x0:", output) + end + + Test.@testset "HamiltonianPointConfig show methods" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) + io = IOBuffer() + show(io, config) + output = String(take!(io)) + Test.@test occursin("HamiltonianPointConfig", output) + Test.@test occursin("t0:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("p0:", output) + Test.@test occursin("tf:", output) + end + + Test.@testset "HamiltonianPointConfig text/plain show method" begin + config = Configs.HamiltonianPointConfig(0.0, [1.0], [0.5], 1.0) + io = IOBuffer() + show(io, MIME("text/plain"), config) + output = String(take!(io)) + Test.@test occursin("HamiltonianPointConfig", output) + Test.@test occursin("t0:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("p0:", output) + Test.@test occursin("tf:", output) + end + + Test.@testset "HamiltonianTrajectoryConfig show methods" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0], [0.5]) + io = IOBuffer() + show(io, config) + output = String(take!(io)) + Test.@test occursin("HamiltonianTrajectoryConfig", output) + Test.@test occursin("tspan:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("p0:", output) + end + + Test.@testset "HamiltonianTrajectoryConfig text/plain show method" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0], [0.5]) + io = IOBuffer() + show(io, MIME("text/plain"), config) + output = String(take!(io)) + Test.@test occursin("HamiltonianTrajectoryConfig", output) + Test.@test occursin("tspan:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("p0:", output) + end + + Test.@testset "AugmentedHamiltonianPointConfig show methods" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0], [0.5], [0.0], 1.0) + io = IOBuffer() + show(io, config) + output = String(take!(io)) + Test.@test occursin("AugmentedHamiltonianPointConfig", output) + Test.@test occursin("t0:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("p0:", output) + Test.@test occursin("pv0:", output) + Test.@test occursin("tf:", output) + end + + Test.@testset "AugmentedHamiltonianPointConfig text/plain show method" begin + config = Configs.AugmentedHamiltonianPointConfig(0.0, [1.0], [0.5], [0.0], 1.0) + io = IOBuffer() + show(io, MIME("text/plain"), config) + output = String(take!(io)) + Test.@test occursin("AugmentedHamiltonianPointConfig", output) + Test.@test occursin("t0:", output) + Test.@test occursin("x0:", output) + Test.@test occursin("p0:", output) + Test.@test occursin("pv0:", output) + Test.@test occursin("tf:", output) + end + end + end +end + +end # module + +test_show_configs() = TestShowConfigs.test_show_configs() diff --git a/test/suite/data/test_abstract_hamiltonian.jl b/test/suite/data/test_abstract_hamiltonian.jl new file mode 100644 index 00000000..aff12f11 --- /dev/null +++ b/test/suite/data/test_abstract_hamiltonian.jl @@ -0,0 +1,143 @@ +module TestAbstractHamiltonian + +import Test +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# ============================================================================== +# Fake type for contract testing (defined at module top-level per testing-creation.md) +# ============================================================================== + +struct FakeHamiltonian{TD, VD} <: Data.AbstractHamiltonian{TD, VD} end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_hamiltonian() + Test.@testset "AbstractHamiltonian Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type Definition + # ==================================================================== + + Test.@testset "Abstract Type Definition" begin + Test.@testset "AbstractHamiltonian exists" begin + Test.@test isdefined(Data, :AbstractHamiltonian) + end + + Test.@testset "AbstractHamiltonian is exported" begin + Test.@test isdefined(Data, :AbstractHamiltonian) + end + + Test.@testset "FakeHamiltonian subtypes AbstractHamiltonian" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test fake isa Data.AbstractHamiltonian + end + end + + # ==================================================================== + # UNIT TESTS - Trait Accessors on Abstract Type + # ==================================================================== + + Test.@testset "Trait Accessors on Abstract Type" begin + Test.@testset "has_time_dependence_trait returns true" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Traits.has_time_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_time_dependence_trait, fake) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Traits.has_variable_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_variable_dependence_trait, fake) === true + end + + Test.@testset "time_dependence returns correct trait" begin + fake_aut = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + fake_nonaut = FakeHamiltonian{Traits.NonAutonomous, Traits.Fixed}() + Test.@test Traits.time_dependence(fake_aut) === Traits.Autonomous + Test.@test Traits.time_dependence(fake_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + fake_fixed = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + fake_nonfixed = FakeHamiltonian{Traits.Autonomous, Traits.NonFixed}() + Test.@test Traits.variable_dependence(fake_fixed) === Traits.Fixed + Test.@test Traits.variable_dependence(fake_nonfixed) === Traits.NonFixed + end + + Test.@testset "explicit dispatch on AbstractHamiltonian methods" begin + fake = FakeHamiltonian{Traits.NonAutonomous, Traits.NonFixed}() + + Test.@test invoke( + Traits.has_time_dependence_trait, + Tuple{Data.AbstractHamiltonian}, + fake, + ) === true + + Test.@test invoke( + Traits.has_variable_dependence_trait, + Tuple{Data.AbstractHamiltonian}, + fake, + ) === true + + Test.@test invoke( + Traits.time_dependence, + Tuple{Data.AbstractHamiltonian{Traits.NonAutonomous, Traits.NonFixed}}, + fake, + ) === Traits.NonAutonomous + + Test.@test invoke( + Traits.variable_dependence, + Tuple{Data.AbstractHamiltonian{Traits.NonAutonomous, Traits.NonFixed}}, + fake, + ) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Liskov Substitution + # ==================================================================== + + Test.@testset "Liskov Substitution" begin + Test.@testset "Hamiltonian is an AbstractHamiltonian" begin + h = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test h isa Data.AbstractHamiltonian + end + end + + # ==================================================================== + # UNIT TESTS - Type Stability + # ==================================================================== + + Test.@testset "Type Stability" begin + Test.@testset "Trait accessors are type-stable" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Test.@inferred(Traits.has_time_dependence_trait(fake)) === true + Test.@test Test.@inferred(Traits.has_variable_dependence_trait(fake)) === true + Test.@test Test.@inferred(Traits.time_dependence(fake)) === Traits.Autonomous + Test.@test Test.@inferred(Traits.variable_dependence(fake)) === Traits.Fixed + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Data, :AbstractHamiltonian) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_hamiltonian() = TestAbstractHamiltonian.test_abstract_hamiltonian() diff --git a/test/suite/data/test_abstract_vector_field.jl b/test/suite/data/test_abstract_vector_field.jl new file mode 100644 index 00000000..7f25504c --- /dev/null +++ b/test/suite/data/test_abstract_vector_field.jl @@ -0,0 +1,158 @@ +module TestAbstractVectorField + +import Test +import CTFlows.Data +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# ============================================================================== +# Fake type for contract testing (defined at module top-level per testing-creation.md) +# ============================================================================== + +struct FakeVectorField{TD, VD, MD} <: Data.AbstractVectorField{TD, VD, MD} end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_vector_field() + Test.@testset "AbstractVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type Definition + # ==================================================================== + + Test.@testset "Abstract Type Definition" begin + Test.@testset "AbstractVectorField exists" begin + Test.@test isdefined(Data, :AbstractVectorField) + end + + Test.@testset "AbstractVectorField is exported" begin + Test.@test isdefined(Data, :AbstractVectorField) + end + + Test.@testset "FakeVectorField subtypes AbstractVectorField" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test fake isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Trait Accessors on Abstract Type + # ==================================================================== + + Test.@testset "Trait Accessors on Abstract Type" begin + Test.@testset "has_time_dependence_trait returns true" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.has_time_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_time_dependence_trait, fake) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.has_variable_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_variable_dependence_trait, fake) === true + end + + Test.@testset "has_mutability_trait returns true" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.has_mutability_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_mutability_trait, fake) === true + end + + Test.@testset "time_dependence returns correct trait" begin + fake_aut = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + fake_nonaut = FakeVectorField{Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.time_dependence(fake_aut) === Traits.Autonomous + Test.@test Traits.time_dependence(fake_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + fake_fixed = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + fake_nonfixed = FakeVectorField{Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace}() + Test.@test Traits.variable_dependence(fake_fixed) === Traits.Fixed + Test.@test Traits.variable_dependence(fake_nonfixed) === Traits.NonFixed + end + + Test.@testset "mutability_trait returns correct trait" begin + fake_oop = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + fake_ip = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.InPlace}() + Test.@test Traits.mutability_trait(fake_oop) === Traits.OutOfPlace + Test.@test Traits.mutability_trait(fake_ip) === Traits.InPlace + end + + Test.@testset "explicit dispatch on AbstractVectorField methods" begin + fake = FakeVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}() + + Test.@test invoke( + Traits.has_time_dependence_trait, + Tuple{Data.AbstractVectorField}, + fake, + ) === true + + Test.@test invoke( + Traits.has_variable_dependence_trait, + Tuple{Data.AbstractVectorField}, + fake, + ) === true + + Test.@test invoke( + Traits.has_mutability_trait, + Tuple{Data.AbstractVectorField}, + fake, + ) === true + + Test.@test invoke( + Traits.time_dependence, + Tuple{Data.AbstractVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}}, + fake, + ) === Traits.NonAutonomous + + Test.@test invoke( + Traits.variable_dependence, + Tuple{Data.AbstractVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}}, + fake, + ) === Traits.NonFixed + + Test.@test invoke( + Traits.mutability_trait, + Tuple{Data.AbstractVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}}, + fake, + ) === Traits.InPlace + end + end + + # ==================================================================== + # UNIT TESTS - Liskov Substitution + # ==================================================================== + + Test.@testset "Liskov Substitution" begin + Test.@testset "VectorField is an AbstractVectorField" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.AbstractVectorField + end + + Test.@testset "HamiltonianVectorField is an AbstractVectorField" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Data, :AbstractVectorField) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_vector_field() = TestAbstractVectorField.test_abstract_vector_field() diff --git a/test/suite/data/test_data_module.jl b/test/suite/data/test_data_module.jl new file mode 100644 index 00000000..1d6ea72c --- /dev/null +++ b/test/suite/data/test_data_module.jl @@ -0,0 +1,196 @@ +""" +# ============================================================================ +# Data Module Exports Tests +# ============================================================================ +# This file tests the exports from the `Data` module. It verifies that +# the expected types and constructors are properly exported by +# `CTFlows.Data` and readily accessible to the end user. +""" + +module TestDataModule + +import Test +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestDataModule + +function test_data_module() + Test.@testset "Data Module Exports" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # VectorField Type + # ==================================================================== + + Test.@testset "VectorField Type" begin + Test.@testset "VectorField is exported" begin + Test.@test isdefined(Data, :VectorField) + Test.@test Data.VectorField <: Data.VectorField + end + + Test.@testset "VectorField constructor is exported" begin + Test.@test isdefined(Data, :VectorField) + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.VectorField + end + + Test.@testset "VectorField with Autonomous trait" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test Traits.time_dependence(vf) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf) === Traits.Fixed + end + + Test.@testset "VectorField with NonAutonomous trait" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + Test.@test Traits.time_dependence(vf) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(vf) === Traits.Fixed + end + + Test.@testset "VectorField with NonFixed trait" begin + vf = Data.VectorField((x, v) -> x .* v; is_autonomous=true, is_variable=true) + Test.@test Traits.time_dependence(vf) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf) === Traits.NonFixed + end + + Test.@testset "VectorField with NonAutonomous and NonFixed traits" begin + vf = Data.VectorField((t, x, v) -> t .* x .* v; is_autonomous=false, is_variable=true) + Test.@test Traits.time_dependence(vf) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(vf) === Traits.NonFixed + end + end + + # ==================================================================== + # Hamiltonian Type + # ==================================================================== + + Test.@testset "Hamiltonian Type" begin + Test.@testset "Hamiltonian is exported" begin + Test.@test isdefined(Data, :Hamiltonian) + Test.@test Data.Hamiltonian <: Data.Hamiltonian + end + + Test.@testset "Hamiltonian constructor is exported" begin + Test.@test isdefined(Data, :Hamiltonian) + h = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test h isa Data.Hamiltonian + end + + Test.@testset "Hamiltonian with Autonomous trait" begin + h = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test Traits.time_dependence(h) === Traits.Autonomous + Test.@test Traits.variable_dependence(h) === Traits.Fixed + end + + Test.@testset "Hamiltonian with NonAutonomous trait" begin + h = Data.Hamiltonian((t, x, p) -> t + x + p; is_autonomous=false, is_variable=false) + Test.@test Traits.time_dependence(h) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(h) === Traits.Fixed + end + + Test.@testset "Hamiltonian with NonFixed trait" begin + h = Data.Hamiltonian((x, p, v) -> x + p + v; is_autonomous=true, is_variable=true) + Test.@test Traits.time_dependence(h) === Traits.Autonomous + Test.@test Traits.variable_dependence(h) === Traits.NonFixed + end + + Test.@testset "Hamiltonian with NonAutonomous and NonFixed traits" begin + h = Data.Hamiltonian((t, x, p, v) -> t + x + p + v; is_autonomous=false, is_variable=true) + Test.@test Traits.time_dependence(h) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(h) === Traits.NonFixed + end + end + + # ==================================================================== + # Trait Support + # ==================================================================== + + Test.@testset "Trait Support" begin + Test.@testset "VectorField has time dependence trait" begin + vf = Data.VectorField(x -> x) + Test.@test Traits.has_time_dependence_trait(vf) + end + + Test.@testset "VectorField has variable dependence trait" begin + vf = Data.VectorField(x -> x) + Test.@test Traits.has_variable_dependence_trait(vf) + end + + Test.@testset "time_dependence function works with VectorField" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test Traits.time_dependence(vf_aut) === Traits.Autonomous + + vf_non = Data.VectorField((t, x) -> x; is_autonomous=false, is_variable=false) + Test.@test Traits.time_dependence(vf_non) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence function works with VectorField" begin + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test Traits.variable_dependence(vf_fixed) === Traits.Fixed + + vf_nonfixed = Data.VectorField((x, v) -> x .* v; is_autonomous=true, is_variable=true) + Test.@test Traits.variable_dependence(vf_nonfixed) === Traits.NonFixed + end + end + + # ==================================================================== + # Call Signatures + # ==================================================================== + + Test.@testset "Call Signatures" begin + Test.@testset "Autonomous Fixed signature" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + result = vf([1.0, 2.0]) + Test.@test result โ‰ˆ [-1.0, -2.0] + end + + Test.@testset "NonAutonomous Fixed signature" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + result = vf(2.0, [1.0, 2.0]) + Test.@test result โ‰ˆ [2.0, 4.0] + end + + Test.@testset "Autonomous NonFixed signature" begin + vf = Data.VectorField((x, v) -> x .* v; is_autonomous=true, is_variable=true) + result = vf([1.0, 2.0], 3.0) + Test.@test result โ‰ˆ [3.0, 6.0] + end + + Test.@testset "NonAutonomous NonFixed signature" begin + vf = Data.VectorField((t, x, v) -> t .* x .* v; is_autonomous=false, is_variable=true) + result = vf(2.0, [1.0, 2.0], 3.0) + Test.@test result โ‰ˆ [6.0, 12.0] + end + + Test.@testset "Uniform (t, x, v) signature works for all traits" begin + # Autonomous Fixed - ignores t and v + vf1 = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + result1 = vf1(0.0, [1.0, 2.0], nothing) + Test.@test result1 โ‰ˆ [-1.0, -2.0] + + # NonAutonomous Fixed - ignores v + vf2 = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + result2 = vf2(2.0, [1.0, 2.0], nothing) + Test.@test result2 โ‰ˆ [2.0, 4.0] + + # Autonomous NonFixed - ignores t + vf3 = Data.VectorField((x, v) -> x .* v; is_autonomous=true, is_variable=true) + result3 = vf3(0.0, [1.0, 2.0], 3.0) + Test.@test result3 โ‰ˆ [3.0, 6.0] + + # NonAutonomous NonFixed - uses all + vf4 = Data.VectorField((t, x, v) -> t .* x .* v; is_autonomous=false, is_variable=true) + result4 = vf4(2.0, [1.0, 2.0], 3.0) + Test.@test result4 โ‰ˆ [6.0, 12.0] + end + end + end +end + +end # module TestDataModule + +# CRITICAL: Redefine in outer scope for TestRunner +test_data_module() = TestDataModule.test_data_module() diff --git a/test/suite/data/test_hamiltonian.jl b/test/suite/data/test_hamiltonian.jl new file mode 100644 index 00000000..66bc321d --- /dev/null +++ b/test/suite/data/test_hamiltonian.jl @@ -0,0 +1,160 @@ +module TestHamiltonian + +import Test +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_hamiltonian() + Test.@testset "Hamiltonian Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction with all trait combinations + # ==================================================================== + + Test.@testset "Unit: Construction with all trait combinations" begin + # Autonomous, Fixed + h_aut_fixed = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test h_aut_fixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_aut_fixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(h_aut_fixed) == Traits.Fixed + + # NonAutonomous, Fixed + h_nonaut_fixed = Data.Hamiltonian((t, x, p) -> t + x + p; is_autonomous=false, is_variable=false) + Test.@test h_nonaut_fixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_nonaut_fixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(h_nonaut_fixed) == Traits.Fixed + + # Autonomous, NonFixed + h_aut_nonfixed = Data.Hamiltonian((x, p, v) -> x + p + v; is_autonomous=true, is_variable=true) + Test.@test h_aut_nonfixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_aut_nonfixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(h_aut_nonfixed) == Traits.NonFixed + + # NonAutonomous, NonFixed + h_nonaut_nonfixed = Data.Hamiltonian((t, x, p, v) -> t + x + p + v; is_autonomous=false, is_variable=true) + Test.@test h_nonaut_nonfixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_nonaut_nonfixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(h_nonaut_nonfixed) == Traits.NonFixed + end + + # ==================================================================== + # UNIT TESTS - Natural call signatures + # ==================================================================== + + Test.@testset "Unit: Natural call signatures" begin + # (x, p) for Autonomous, Fixed + h1 = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + result = h1([1.0, 2.0], [3.0, 4.0]) + Test.@test result == [4.0, 6.0] + + # (t, x, p) for NonAutonomous, Fixed + h2 = Data.Hamiltonian((t, x, p) -> x .+ p; is_autonomous=false, is_variable=false) + result = h2(2.0, [1.0, 2.0], [3.0, 4.0]) + Test.@test result == [4.0, 6.0] + + # (x, p, v) for Autonomous, NonFixed + h3 = Data.Hamiltonian((x, p, v) -> x .+ p .+ v; is_autonomous=true, is_variable=true) + result = h3([1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + + # (t, x, p, v) for NonAutonomous, NonFixed + h4 = Data.Hamiltonian((t, x, p, v) -> x .+ p .+ v; is_autonomous=false, is_variable=true) + result = h4(2.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + end + + # ==================================================================== + # UNIT TESTS - Uniform call signature + # ==================================================================== + + Test.@testset "Unit: Uniform call signature (t, x, p, v)" begin + # Autonomous Fixed - ignores t and v + h1 = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + result = h1(0.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test result == [4.0, 6.0] + + # NonAutonomous Fixed - ignores v + h2 = Data.Hamiltonian((t, x, p) -> x .+ p; is_autonomous=false, is_variable=false) + result = h2(2.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test result == [4.0, 6.0] + + # Autonomous NonFixed - ignores t + h3 = Data.Hamiltonian((x, p, v) -> x .+ p .+ v; is_autonomous=true, is_variable=true) + result = h3(0.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + + # NonAutonomous NonFixed - uses all + h4 = Data.Hamiltonian((t, x, p, v) -> x .+ p .+ v; is_autonomous=false, is_variable=true) + result = h4(2.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + end + + # ==================================================================== + # UNIT TESTS - Type stability + # ==================================================================== + + Test.@testset "Unit: Type stability" begin + # Uniform call type stability + h = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + Test.@test Test.@inferred(h(0.0, [1.0, 2.0], [3.0, 4.0], nothing)) == [4.0, 6.0] + end + + # ==================================================================== + # UNIT TESTS - Show Methods + # ==================================================================== + + Test.@testset "Show Methods" begin + h = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, h) + str = String(take!(io)) + Test.@test occursin("Hamiltonian", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + Test.@test occursin("natural call", str) + Test.@test occursin("uniform call", str) + end + + Test.@testset "Base.show (text/plain)" begin + io = IOBuffer() + show(io, MIME("text/plain"), h) + str = String(take!(io)) + Test.@test occursin("Hamiltonian", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "Hamiltonian is an AbstractHamiltonian" begin + h = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + Test.@test h isa Data.AbstractHamiltonian + end + end + + # ==================================================================== + # EXPORTS TESTS + # ==================================================================== + + Test.@testset "Exports" begin + Test.@testset "Hamiltonian is exported" begin + Test.@test isdefined(Data, :Hamiltonian) + end + end + end +end + +end # module TestHamiltonian + +test_hamiltonian() = TestHamiltonian.test_hamiltonian() diff --git a/test/suite/data/test_hamiltonian_vector_field.jl b/test/suite/data/test_hamiltonian_vector_field.jl new file mode 100644 index 00000000..b86bb14f --- /dev/null +++ b/test/suite/data/test_hamiltonian_vector_field.jl @@ -0,0 +1,357 @@ +module TestHamiltonianVectorField + +import Test +import CTFlows.Data: Data +import CTFlows.Common: Common +import CTFlows.Traits: Traits +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# TOP-LEVEL: Fake function with multiple methods for testing +_multi_method_hvf(x::Int, p) = (x, -p) +_multi_method_hvf(x::Float64, p) = (x, -p) +_multi_method_hvf(x::AbstractVector, p) = (x, -p) + +# TOP-LEVEL: Fake function with invalid arity for testing error branches +_bad_arity_hvf(x) = (x, -x) + +function test_hamiltonian_vector_field() + Test.@testset "Hamiltonian Vector Field Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + # Autonomous, Fixed + hvf_autonomous_fixed = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf_autonomous_fixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_autonomous_fixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(hvf_autonomous_fixed) == Traits.Fixed + + # NonAutonomous, Fixed + hvf_nonautonomous_fixed = Data.HamiltonianVectorField((t, x, p) -> (x, -p); is_autonomous=false, is_variable=false) + Test.@test hvf_nonautonomous_fixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_nonautonomous_fixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(hvf_nonautonomous_fixed) == Traits.Fixed + + # Autonomous, NonFixed + hvf_autonomous_nonfixed = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_autonomous=true, is_variable=true) + Test.@test hvf_autonomous_nonfixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_autonomous_nonfixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(hvf_autonomous_nonfixed) == Traits.NonFixed + + # NonAutonomous, NonFixed + hvf_nonautonomous_nonfixed = Data.HamiltonianVectorField((t, x, p, v) -> (x .* v, -p); is_autonomous=false, is_variable=true) + Test.@test hvf_nonautonomous_nonfixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_nonautonomous_nonfixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(hvf_nonautonomous_nonfixed) == Traits.NonFixed + end + + # ==================================================================== + # UNIT TESTS - Natural signatures + # ==================================================================== + + Test.@testset "Natural Signatures" begin + # (x, p) for Autonomous, Fixed + hvf1 = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + dx, dp = hvf1([1.0, 2.0], [3.0, 4.0]) + Test.@test dx == [1.0, 2.0] + Test.@test dp == [-3.0, -4.0] + + # (t, x, p) for NonAutonomous, Fixed + hvf2 = Data.HamiltonianVectorField((t, x, p) -> (t .* x, -p); is_autonomous=false, is_variable=false) + dx, dp = hvf2(2.0, [1.0, 2.0], [3.0, 4.0]) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + # (x, p, v) for Autonomous, NonFixed + hvf3 = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_autonomous=true, is_variable=true) + dx, dp = hvf3([1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + # (t, x, p, v) for NonAutonomous, NonFixed + hvf4 = Data.HamiltonianVectorField((t, x, p, v) -> (t .* x .* v, -p); is_autonomous=false, is_variable=true) + dx, dp = hvf4(2.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test dx == [4.0, 8.0] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Uniform signature + # ==================================================================== + + Test.@testset "Uniform Signature" begin + # All combinations should work with (t, x, p, v) + hvf_autonomous_fixed = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + dx, dp = hvf_autonomous_fixed(0.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test dx == [1.0, 2.0] + Test.@test dp == [-3.0, -4.0] + + hvf_nonautonomous_fixed = Data.HamiltonianVectorField((t, x, p) -> (t .* x, -p); is_autonomous=false, is_variable=false) + dx, dp = hvf_nonautonomous_fixed(2.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + hvf_autonomous_nonfixed = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_autonomous=true, is_variable=true) + dx, dp = hvf_autonomous_nonfixed(0.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Trait accessors + # ==================================================================== + + Test.@testset "Trait Accessors" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test Traits.has_time_dependence_trait(hvf) == true + Test.@test Traits.has_variable_dependence_trait(hvf) == true + Test.@test Traits.time_dependence(hvf) == Traits.Autonomous + Test.@test Traits.variable_dependence(hvf) == Traits.Fixed + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "HamiltonianVectorField is an AbstractVectorField" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + # Just check that show doesn't throw + Test.@test_nowarn sprint(show, hvf) + end + + # ==================================================================== + # UNIT TESTS - Explicit is_inplace parameter + # ==================================================================== + + Test.@testset "Explicit is_inplace parameter" begin + Test.@testset "is_inplace=true creates InPlace HamiltonianVectorField" begin + # Define an out-of-place function but force InPlace + f(x, p) = (x, -p) + hvf = Data.HamiltonianVectorField(f; is_inplace=true) + Test.@test Traits.mutability_trait(hvf) === Traits.InPlace + end + + Test.@testset "is_inplace=false creates OutOfPlace HamiltonianVectorField" begin + # Define an in-place function but force OutOfPlace + f(x, p) = (x, -p) + hvf = Data.HamiltonianVectorField(f; is_inplace=false) + Test.@test Traits.mutability_trait(hvf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - PreconditionError for multiple methods + # ==================================================================== + + Test.@testset "PreconditionError for multiple methods" begin + Test.@testset "Throws PreconditionError when is_inplace is not specified" begin + Test.@test_throws Exceptions.PreconditionError Data.HamiltonianVectorField(_multi_method_hvf) + end + + Test.@testset "No error when is_inplace is explicitly specified" begin + hvf = Data.HamiltonianVectorField(_multi_method_hvf; is_inplace=false) + Test.@test Traits.mutability_trait(hvf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helpers + # ==================================================================== + + Test.@testset "Internal Helpers" begin + Test.@testset "_oop_arity_hvf" begin + Test.@test Data._oop_arity_hvf(Traits.Autonomous, Traits.Fixed) == 2 + Test.@test Data._oop_arity_hvf(Traits.NonAutonomous, Traits.Fixed) == 3 + Test.@test Data._oop_arity_hvf(Traits.Autonomous, Traits.NonFixed) == 3 + Test.@test Data._oop_arity_hvf(Traits.NonAutonomous, Traits.NonFixed) == 4 + end + + Test.@testset "_natural_sig_hvf helpers" begin + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x, p)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x, p)" + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, dp, x, p)" + Test.@test Data._uniform_sig_hvf(Traits.OutOfPlace) == "f(t, x, p, v)" + Test.@test Data._uniform_sig_hvf(Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + + Test.@testset "_detect_mutability_hvf invalid arity" begin + Test.@test_throws Exceptions.IncorrectArgument Data._detect_mutability_hvf(_bad_arity_hvf, Traits.Autonomous, Traits.Fixed) + end + end + + # ==================================================================== + # UNIT TESTS - InPlace Call Signatures + # ==================================================================== + + Test.@testset "InPlace Call Signatures" begin + Test.@testset "Autonomous Fixed - (dx, dp, x, p)" begin + f(dx, dp, x, p) = (dx .= -x; dp .= -p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 3.0, 1.0) + Test.@test dx[1] == -3.0 + Test.@test dp[1] == -1.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, [1.0, 2.0], [0.5, 1.0]) + Test.@test dx == [-1.0, -2.0] + Test.@test dp == [-0.5, -1.0] + end + end + + Test.@testset "NonAutonomous Fixed - (dx, dp, t, x, p)" begin + f(dx, dp, t, x, p) = (dx .= t .* x; dp .= t .* p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 2.0, 3.0, 1.0) + Test.@test dx[1] == 6.0 + Test.@test dp[1] == 2.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0]) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [1.0, 2.0] + end + end + + Test.@testset "Autonomous NonFixed - (dx, dp, x, p, v)" begin + f(dx, dp, x, p, v) = (dx .= x .+ v; dp .= p .+ v) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 3.0, 1.0, 0.5) + Test.@test dx[1] == 3.5 + Test.@test dp[1] == 1.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, [1.0, 2.0], [0.5, 1.0], 0.5) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [1.0, 1.5] + end + end + + Test.@testset "NonAutonomous NonFixed - (dx, dp, t, x, p, v)" begin + f(dx, dp, t, x, p, v) = (dx .= t .* x .+ v; dp .= t .* p .+ v) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 2.0, 3.0, 1.0, 0.5) + Test.@test dx[1] == 6.5 + Test.@test dp[1] == 2.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], 0.5) + Test.@test dx == [2.5, 4.5] + Test.@test dp == [1.5, 2.5] + end + end + end + + # ==================================================================== + # UNIT TESTS - Uniform InPlace Call Signature + # ==================================================================== + + Test.@testset "Uniform InPlace Signature" begin + Test.@testset "Autonomous Fixed InPlace uniform" begin + f(dx, dp, x, p) = (dx .= -x; dp .= -p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], 0.0) + Test.@test dx == [-1.0, -2.0] + Test.@test dp == [-0.5, -1.0] + end + + Test.@testset "NonAutonomous Fixed InPlace uniform" begin + f(dx, dp, t, x, p) = (dx .= t .* x; dp .= t .* p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], 0.0) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [1.0, 2.0] + end + + Test.@testset "Autonomous NonFixed InPlace uniform" begin + f(dx, dp, x, p, v) = (dx .= x .+ v; dp .= p .+ v) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], 0.5) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [1.0, 1.5] + end + end + + Test.@testset "Show Methods" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, hvf) + str = String(take!(io)) + Test.@test occursin("HamiltonianVectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + Test.@test occursin("out-of-place", str) + Test.@test occursin("natural call", str) + Test.@test occursin("uniform call", str) + end + + Test.@testset "Base.show (text/plain)" begin + io = IOBuffer() + show(io, MIME("text/plain"), hvf) + str = String(take!(io)) + Test.@test occursin("HamiltonianVectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_hamiltonian_vector_field() = TestHamiltonianVectorField.test_hamiltonian_vector_field() diff --git a/test/suite/data/test_helpers.jl b/test/suite/data/test_helpers.jl new file mode 100644 index 00000000..661fb6dc --- /dev/null +++ b/test/suite/data/test_helpers.jl @@ -0,0 +1,123 @@ +module TestHelpers + +import Test +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_helpers() + Test.@testset "Helpers Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Label Helpers + # ==================================================================== + + Test.@testset "Label Helpers" begin + Test.@testset "_td_label" begin + Test.@test Data._td_label(Traits.Autonomous) == "autonomous" + Test.@test Data._td_label(Traits.NonAutonomous) == "non-autonomous" + end + + Test.@testset "_vd_label" begin + Test.@test Data._vd_label(Traits.Fixed) == "fixed (no variable)" + Test.@test Data._vd_label(Traits.NonFixed) == "variable" + end + + Test.@testset "_md_label" begin + Test.@test Data._md_label(Traits.OutOfPlace) == "out-of-place" + Test.@test Data._md_label(Traits.InPlace) == "in-place" + end + end + + # ==================================================================== + # UNIT TESTS - VectorField Signature Helpers + # ==================================================================== + + Test.@testset "VectorField Signature Helpers" begin + Test.@testset "_natural_sig_vf - OutOfPlace" begin + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x)" + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(x, v)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(t, x, v)" + end + + Test.@testset "_natural_sig_vf - InPlace" begin + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, x)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.InPlace) == "f(dx, t, x)" + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, x, v)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, t, x, v)" + end + + Test.@testset "_uniform_sig_vf" begin + Test.@test Data._uniform_sig_vf(Traits.OutOfPlace) == "f(t, x, v)" + Test.@test Data._uniform_sig_vf(Traits.InPlace) == "f(dx, t, x, v)" + end + end + + # ==================================================================== + # UNIT TESTS - HamiltonianVectorField Signature Helpers + # ==================================================================== + + Test.@testset "HamiltonianVectorField Signature Helpers" begin + Test.@testset "_natural_sig_hvf - OutOfPlace" begin + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x, p)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x, p)" + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(x, p, v)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(t, x, p, v)" + end + + Test.@testset "_natural_sig_hvf - InPlace" begin + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, dp, x, p)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.InPlace) == "f(dx, dp, t, x, p)" + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, dp, x, p, v)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + + Test.@testset "_uniform_sig_hvf" begin + Test.@test Data._uniform_sig_hvf(Traits.OutOfPlace) == "f(t, x, p, v)" + Test.@test Data._uniform_sig_hvf(Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + + Test.@testset "Uniform signatures" begin + Test.@testset "VectorField uniform signatures" begin + Test.@test Data._uniform_sig_vf(Traits.OutOfPlace) == "f(t, x, v)" + Test.@test Data._uniform_sig_vf(Traits.InPlace) == "f(dx, t, x, v)" + end + + Test.@testset "HamiltonianVectorField uniform signatures" begin + Test.@test Data._uniform_sig_hvf(Traits.OutOfPlace) == "f(t, x, p, v)" + Test.@test Data._uniform_sig_hvf(Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + end + + Test.@testset "Direct label calls" begin + Test.@testset "Time dependence labels" begin + Test.@test Data._td_label(Traits.Autonomous) == "autonomous" + Test.@test Data._td_label(Traits.NonAutonomous) == "non-autonomous" + end + + Test.@testset "Variable dependence labels" begin + Test.@test Data._vd_label(Traits.Fixed) == "fixed (no variable)" + Test.@test Data._vd_label(Traits.NonFixed) == "variable" + end + + Test.@testset "Mutability labels" begin + Test.@test Data._md_label(Traits.OutOfPlace) == "out-of-place" + Test.@test Data._md_label(Traits.InPlace) == "in-place" + end + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_helpers() = TestHelpers.test_helpers() diff --git a/test/suite/data/test_vector_field.jl b/test/suite/data/test_vector_field.jl new file mode 100644 index 00000000..8685653e --- /dev/null +++ b/test/suite/data/test_vector_field.jl @@ -0,0 +1,480 @@ +module TestVectorField + +import Test +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# TOP-LEVEL: Fake function with multiple methods for testing +_multi_method_f(x::Int) = -x +_multi_method_f(x::Float64) = -x +_multi_method_f(x::AbstractVector) = -x + +# TOP-LEVEL: Fake function with invalid arity for testing error branches +_bad_arity_f(x, y, z) = x + y + z + +# ============================================================================== +# Test function +# ============================================================================== + +function test_vector_field() + Test.@testset "VectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.VectorField + end + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + Test.@testset "keyword constructor with defaults" begin + vf = Data.VectorField(x -> x) + Test.@test vf isa Data.VectorField + Test.@test Traits.time_dependence(vf) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf) === Traits.Fixed + end + + Test.@testset "keyword constructor with explicit flags" begin + vf_autonomous = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test Traits.time_dependence(vf_autonomous) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf_autonomous) === Traits.Fixed + + vf_nonautonomous = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + Test.@test Traits.time_dependence(vf_nonautonomous) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(vf_nonautonomous) === Traits.Fixed + + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + Test.@test Traits.time_dependence(vf_nonfixed) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf_nonfixed) === Traits.NonFixed + + vf_full = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + Test.@test Traits.time_dependence(vf_full) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(vf_full) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Trait Methods + # ==================================================================== + + Test.@testset "Trait Methods" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonaut = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "has_time_dependence_trait returns true" begin + Test.@test Traits.has_time_dependence_trait(vf_aut) === true + Test.@test Traits.has_time_dependence_trait(vf_nonaut) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + Test.@test Traits.has_variable_dependence_trait(vf_fixed) === true + Test.@test Traits.has_variable_dependence_trait(vf_nonfixed) === true + end + + Test.@testset "time_dependence returns correct trait" begin + Test.@test Traits.time_dependence(vf_aut) === Traits.Autonomous + Test.@test Traits.time_dependence(vf_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + Test.@test Traits.variable_dependence(vf_fixed) === Traits.Fixed + Test.@test Traits.variable_dependence(vf_nonfixed) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Natural Call Signatures + # ==================================================================== + + Test.@testset "Natural Call Signatures" begin + Test.@testset "Autonomous Fixed - (x)" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(3.0) == -3.0 + end + + Test.@testset "vector" begin + Test.@test vf([1.0, 2.0]) == [-1.0, -2.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(x0) + Test.@test result == -x0 + end + end + + Test.@testset "NonAutonomous Fixed - (t, x)" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(2.0, 3.0) == 6.0 + end + + Test.@testset "vector" begin + Test.@test vf(2.0, [1.0, 2.0]) == [2.0, 4.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(2.0, x0) + Test.@test result == 2 .* x0 + end + end + + Test.@testset "Autonomous NonFixed - (x, v)" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "scalar" begin + Test.@test vf(3.0, 0.5) == 3.5 + end + + Test.@testset "vector" begin + Test.@test vf([1.0, 2.0], 0.5) == [1.5, 2.5] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(x0, 0.5) + Test.@test result == x0 .+ 0.5 + end + end + + Test.@testset "NonAutonomous NonFixed - (t, x, v)" begin + vf = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + + Test.@testset "scalar" begin + Test.@test vf(2.0, 3.0, 0.5) == 6.5 + end + + Test.@testset "vector" begin + Test.@test vf(2.0, [1.0, 2.0], 0.5) == [2.5, 4.5] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(2.0, x0, 0.5) + Test.@test result == 2 .* x0 .+ 0.5 + end + end + end + + # ==================================================================== + # UNIT TESTS - Uniform Call Signature (t, x, v) + # ==================================================================== + + Test.@testset "Uniform Call Signature" begin + Test.@testset "Autonomous Fixed ignores t and v" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(0.0, 3.0, 0.0) == -3.0 + end + + Test.@testset "vector" begin + Test.@test vf(0.0, [1.0, 2.0], 0.0) == [-1.0, -2.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(0.0, x0, 0.0) + Test.@test result == -x0 + end + end + + Test.@testset "NonAutonomous Fixed uses t, ignores v" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(2.0, 3.0, 0.0) == 6.0 + end + + Test.@testset "vector" begin + Test.@test vf(2.0, [1.0, 2.0], 0.0) == [2.0, 4.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(2.0, x0, 0.0) + Test.@test result == 2 .* x0 + end + end + + Test.@testset "Autonomous NonFixed ignores t, uses v" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "scalar" begin + Test.@test vf(0.0, 3.0, 0.5) == 3.5 + end + + Test.@testset "vector" begin + Test.@test vf(0.0, [1.0, 2.0], 0.5) == [1.5, 2.5] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(0.0, x0, 0.5) + Test.@test result == x0 .+ 0.5 + end + end + end + + # ==================================================================== + # UNIT TESTS - InPlace Call Signatures + # ==================================================================== + + Test.@testset "InPlace Call Signatures" begin + Test.@testset "Autonomous Fixed - (dx, x)" begin + f(dx, x) = dx .= -x + vf = Data.VectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + x = 3.0 + vf(dx, x) + Test.@test dx[1] == -3.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + x = [1.0, 2.0] + vf(dx, x) + Test.@test dx == [-1.0, -2.0] + end + end + + Test.@testset "NonAutonomous Fixed - (dx, t, x)" begin + f(dx, t, x) = dx .= t .* x + vf = Data.VectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + vf(dx, 2.0, 3.0) + Test.@test dx[1] == 6.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0]) + Test.@test dx == [2.0, 4.0] + end + end + + Test.@testset "Autonomous NonFixed - (dx, x, v)" begin + f(dx, x, v) = dx .= x .+ v + vf = Data.VectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + vf(dx, 3.0, 0.5) + Test.@test dx[1] == 3.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + vf(dx, [1.0, 2.0], 0.5) + Test.@test dx == [1.5, 2.5] + end + end + + Test.@testset "NonAutonomous NonFixed - (dx, t, x, v)" begin + f(dx, t, x, v) = dx .= t .* x .+ v + vf = Data.VectorField(f; is_autonomous=false, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + vf(dx, 2.0, 3.0, 0.5) + Test.@test dx[1] == 6.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0], 0.5) + Test.@test dx == [2.5, 4.5] + end + end + end + + # ==================================================================== + # UNIT TESTS - Uniform InPlace Call Signature + # ==================================================================== + + Test.@testset "Uniform InPlace Signature" begin + Test.@testset "Autonomous Fixed InPlace uniform" begin + f(dx, x) = dx .= -x + vf = Data.VectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 0.0, [1.0, 2.0], 0.0) + Test.@test dx == [-1.0, -2.0] + end + + Test.@testset "NonAutonomous Fixed InPlace uniform" begin + f(dx, t, x) = dx .= t .* x + vf = Data.VectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0], 0.0) + Test.@test dx == [2.0, 4.0] + end + + Test.@testset "Autonomous NonFixed InPlace uniform" begin + f(dx, x, v) = dx .= x .+ v + vf = Data.VectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 0.0, [1.0, 2.0], 0.5) + Test.@test dx == [1.5, 2.5] + end + end + + Test.@testset "Common Trait Predicates" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonaut = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "is_autonomous / is_nonautonomous" begin + Test.@test Traits.is_autonomous(vf_aut) === true + Test.@test Traits.is_nonautonomous(vf_aut) === false + Test.@test Traits.is_autonomous(vf_nonaut) === false + Test.@test Traits.is_nonautonomous(vf_nonaut) === true + end + + Test.@testset "is_variable / is_nonvariable" begin + Test.@test Traits.is_variable(vf_fixed) === false + Test.@test Traits.is_nonvariable(vf_fixed) === true + Test.@test Traits.is_variable(vf_nonfixed) === true + Test.@test Traits.is_nonvariable(vf_nonfixed) === false + end + end + + # ==================================================================== + # UNIT TESTS - Show Methods + # ==================================================================== + + Test.@testset "Show Methods" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, vf) + str = String(take!(io)) + Test.@test occursin("VectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + Test.@test occursin("out-of-place", str) + Test.@test occursin("natural call", str) + Test.@test occursin("uniform call", str) + end + + Test.@testset "Base.show (text/plain)" begin + io = IOBuffer() + show(io, MIME("text/plain"), vf) + str = String(take!(io)) + Test.@test occursin("VectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "VectorField is an AbstractVectorField" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Data, :VectorField) + end + end + + # ==================================================================== + # UNIT TESTS - Explicit is_inplace parameter + # ==================================================================== + + Test.@testset "Explicit is_inplace parameter" begin + Test.@testset "is_inplace=true creates InPlace VectorField" begin + # Define an out-of-place function but force InPlace + f(x) = -x + vf = Data.VectorField(f; is_inplace=true) + Test.@test Traits.mutability_trait(vf) === Traits.InPlace + end + + Test.@testset "is_inplace=false creates OutOfPlace VectorField" begin + # Define an in-place function but force OutOfPlace + f(x) = -x + vf = Data.VectorField(f; is_inplace=false) + Test.@test Traits.mutability_trait(vf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - PreconditionError for multiple methods + # ==================================================================== + + Test.@testset "PreconditionError for multiple methods" begin + Test.@testset "Throws PreconditionError when is_inplace is not specified" begin + Test.@test_throws Exceptions.PreconditionError Data.VectorField(_multi_method_f) + end + + Test.@testset "No error when is_inplace is explicitly specified" begin + vf = Data.VectorField(_multi_method_f; is_inplace=false) + Test.@test Traits.mutability_trait(vf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helpers + # ==================================================================== + + Test.@testset "Internal Helpers" begin + Test.@testset "_oop_arity_vf" begin + Test.@test Data._oop_arity_vf(Traits.Autonomous, Traits.Fixed) == 1 + Test.@test Data._oop_arity_vf(Traits.NonAutonomous, Traits.Fixed) == 2 + Test.@test Data._oop_arity_vf(Traits.Autonomous, Traits.NonFixed) == 2 + Test.@test Data._oop_arity_vf(Traits.NonAutonomous, Traits.NonFixed) == 3 + end + + Test.@testset "_natural_sig_vf helpers" begin + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x)" + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, x)" + Test.@test Data._uniform_sig_vf(Traits.OutOfPlace) == "f(t, x, v)" + Test.@test Data._uniform_sig_vf(Traits.InPlace) == "f(dx, t, x, v)" + end + + Test.@testset "_detect_mutability_vf invalid arity" begin + Test.@test_throws Exceptions.IncorrectArgument Data._detect_mutability_vf(_bad_arity_f, Traits.Autonomous, Traits.Fixed) + end + end + end +end + +end # module + +test_vector_field() = TestVectorField.test_vector_field() diff --git a/test/suite/differentiation/test_ad_backend.jl b/test/suite/differentiation/test_ad_backend.jl new file mode 100644 index 00000000..70f1d445 --- /dev/null +++ b/test/suite/differentiation/test_ad_backend.jl @@ -0,0 +1,142 @@ +""" +Unit and error tests for the AD backend contract and DifferentiationInterface strategy. +""" + +module TestADBackend + +import Test +import CTFlows.Differentiation +import CTBase.Exceptions +import CTSolvers +import ADTypes + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake AD Backend for Testing (at module top-level) +# ============================================================================== + +struct FakeADBackend <: Differentiation.AbstractADBackend + options::CTSolvers.Strategies.StrategyOptions +end + +FakeADBackend() = FakeADBackend(CTSolvers.Strategies.StrategyOptions()) + +function test_ad_backend() + Test.@testset "AD Backend Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ============================================================================== + # Unit Tests + # ============================================================================== + + Test.@testset "Unit: AbstractADBackend abstract type" begin + backend = FakeADBackend() + Test.@test backend isa Differentiation.AbstractADBackend + Test.@test backend isa CTSolvers.Strategies.AbstractStrategy + end + + Test.@testset "Unit: DifferentiationInterface construction" begin + # Default construction + di = Differentiation.DifferentiationInterface() + Test.@test di isa Differentiation.DifferentiationInterface + Test.@test di isa Differentiation.AbstractADBackend + + # Default backend is AutoForwardDiff + metadata = CTSolvers.Strategies.metadata(Differentiation.DifferentiationInterface) + Test.@test metadata[:ad_backend].default === ADTypes.AutoForwardDiff() + + # Custom backend + di_custom = Differentiation.DifferentiationInterface(backend=ADTypes.AutoZygote()) + Test.@test di_custom isa Differentiation.DifferentiationInterface + end + + Test.@testset "Unit: CTSolvers.Strategies contract" begin + # id + Test.@test CTSolvers.Strategies.id(Differentiation.DifferentiationInterface) === + :di + + # description + desc = CTSolvers.Strategies.description(Differentiation.DifferentiationInterface) + Test.@test desc isa String + Test.@test !isempty(desc) + + # metadata + metadata = CTSolvers.Strategies.metadata(Differentiation.DifferentiationInterface) + Test.@test metadata isa CTSolvers.Strategies.StrategyMetadata + Test.@test length(metadata) > 0 + Test.@test :ad_backend in keys(metadata) + Test.@test :prepare_cache in keys(metadata) + Test.@test metadata[:prepare_cache].default === false + end + + Test.@testset "Unit: build_ad_backend" begin + backend = Differentiation.build_ad_backend() + Test.@test backend isa Differentiation.DifferentiationInterface + end + + # ============================================================================== + # Error Tests + # ============================================================================== + + Test.@testset "Error: hamiltonian_gradient stub throws NotImplemented" begin + backend = FakeADBackend() + h = nothing # Dummy Hamiltonian + t = 0.0 + x = [1.0, 2.0] + p = [3.0, 4.0] + v = 5.0 + cache = nothing + + try + Differentiation.hamiltonian_gradient(backend, h, t, x, p, v, cache) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("hamiltonian_gradient", err.required_method) + end + end + + Test.@testset "Error: variable_gradient stub throws NotImplemented" begin + backend = FakeADBackend() + h = nothing + t = 0.0 + x = [1.0, 2.0] + p = [3.0, 4.0] + v = 5.0 + cache = nothing + + try + Differentiation.variable_gradient(backend, h, t, x, p, v, cache) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("variable_gradient", err.required_method) + end + end + + Test.@testset "Error: prepare_cache stub throws NotImplemented" begin + backend = FakeADBackend() + h = nothing + typical_t = 0.0 + typical_x = [1.0, 2.0] + typical_p = [3.0, 4.0] + typical_v = 5.0 + + try + Differentiation.prepare_cache(backend, h, typical_t, typical_x, typical_p, typical_v) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("prepare_cache", err.required_method) + end + end + + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_ad_backend() = TestADBackend.test_ad_backend() + diff --git a/test/suite/differentiation/test_differentiation_module.jl b/test/suite/differentiation/test_differentiation_module.jl new file mode 100644 index 00000000..fa446f1d --- /dev/null +++ b/test/suite/differentiation/test_differentiation_module.jl @@ -0,0 +1,39 @@ +""" +Module loading and exports tests for the Differentiation submodule. +""" + +module TestDifferentiationModule + +import Test +import CTFlows +import CTFlows.Differentiation + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_differentiation_module() + Test.@testset "Differentiation Module Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + Test.@testset "Exports Verification" begin + # Verify all expected exports are defined + Test.@test isdefined(Differentiation, :AbstractADBackend) + Test.@test isdefined(Differentiation, :DifferentiationInterface) + Test.@test isdefined(Differentiation, :build_ad_backend) + Test.@test isdefined(Differentiation, :hamiltonian_gradient) + Test.@test isdefined(Differentiation, :variable_gradient) + Test.@test isdefined(Differentiation, :prepare_cache) + end + + Test.@testset "Module Loading" begin + # Verify the Differentiation submodule is loaded + Test.@test CTFlows.Differentiation isa Module + end + + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_differentiation_module() = TestDifferentiationModule.test_differentiation_module() + diff --git a/test/suite/extensions/test_differentiation_interface_extension.jl b/test/suite/extensions/test_differentiation_interface_extension.jl new file mode 100644 index 00000000..faabb92e --- /dev/null +++ b/test/suite/extensions/test_differentiation_interface_extension.jl @@ -0,0 +1,168 @@ +""" +Unit and integration tests for the CTFlowsDifferentiationInterface extension. +""" + +module TestDifferentiationInterfaceExtension + +import Test +import CTFlows: CTFlows +import CTFlows.Data: Data +import CTFlows.Common: Common +import CTFlows.Differentiation: Differentiation +import ADTypes +import DifferentiationInterface + +const CTFlowsDifferentiationInterface = Base.get_extension(CTFlows, :CTFlowsDifferentiationInterface) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake Hamiltonian for testing (module top-level, not inside test functions) +# ============================================================================== + +# Simple quadratic Hamiltonian: H(t, x, p, v) = 0.5*(sum(x.^2) + sum(p.^2)) +# Gradient: โˆ‚H/โˆ‚x = x, โˆ‚H/โˆ‚p = p +const FAKE_HAMILTONIAN_FIXED = Data.Hamiltonian( + (x, p) -> 0.5 * (sum(abs2, x) + sum(abs2, p)); + is_autonomous=true, is_variable=false) + +# Gradient: โˆ‚H/โˆ‚x = x, โˆ‚H/โˆ‚p = p, โˆ‚H/โˆ‚v = v +const FAKE_HAMILTONIAN_NONFIXED = Data.Hamiltonian( + (x, p, v) -> 0.5 * (sum(abs2, x) + sum(abs2, p) + sum(abs2, v)); + is_autonomous=true, is_variable=true) + +# ============================================================================== +# Test function +# ============================================================================== + +function test_differentiation_interface_extension() + Test.@testset "DifferentiationInterface Extension Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # Unit Tests + # ==================================================================== + + Test.@testset "Unit: DifferentiationInterfaceCache construction" begin + # Create a cache with dummy preps (nothing for now since we can't create real DI plans without the extension loaded) + cache = CTFlowsDifferentiationInterface.DifferentiationInterfaceCache(nothing, nothing, nothing, nothing, nothing, nothing) + Test.@test cache isa Common.AbstractCache + end + + # ==================================================================== + # Integration Tests - require DifferentiationInterface loaded + # ==================================================================== + Test.@testset "Integration: prepare_cache with prepare_cache=true" begin + backend = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=true) + typical_t = 0.0 + typical_x = [1.0, 2.0] + typical_p = [3.0, 4.0] + typical_v = nothing # Fixed + + cache = Differentiation.prepare_cache(backend, FAKE_HAMILTONIAN_FIXED, typical_t, typical_x, typical_p, typical_v) + Test.@test cache !== nothing + Test.@test cache isa CTFlowsDifferentiationInterface.DifferentiationInterfaceCache + # p_v should be nothing for Fixed problems + Test.@test cache.p_v === nothing + end + + Test.@testset "Integration: prepare_cache with prepare_cache=false" begin + backend = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=false) + typical_t = 0.0 + typical_x = [1.0, 2.0] + typical_p = [3.0, 4.0] + typical_v = nothing + + cache = Differentiation.prepare_cache(backend, FAKE_HAMILTONIAN_FIXED, typical_t, typical_x, typical_p, typical_v) + Test.@test cache === nothing + end + + Test.@testset "Integration: hamiltonian_gradient (no cache)" begin + # For unit testing, we verify the gradient methods are callable + # Actual gradient computation is tested in integration tests with real ODEs + backend = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=false) + t = 0.0 + x = [1.0, 2.0] + p = [3.0, 4.0] + v = nothing + + # Call the method - it should not error + grad_x, grad_p = Differentiation.hamiltonian_gradient(backend, FAKE_HAMILTONIAN_FIXED, t, x, p, v, nothing) + # Verify the method returns a tuple of gradients + Test.@test grad_x isa AbstractVector + Test.@test length(grad_x) == length(x) + Test.@test grad_x โ‰ˆ x atol=1e-8 + Test.@test grad_p isa AbstractVector + Test.@test length(grad_p) == length(p) + Test.@test grad_p โ‰ˆ p atol=1e-8 + end + + Test.@testset "Integration: hamiltonian_gradient (with cache)" begin + # For unit testing, we verify the gradient methods are callable with cache + backend = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=true) + typical_t = 0.0 + typical_x = [1.0, 2.0] + typical_p = [3.0, 4.0] + typical_v = nothing + + cache = Differentiation.prepare_cache(backend, FAKE_HAMILTONIAN_FIXED, typical_t, typical_x, typical_p, typical_v) + Test.@test cache !== nothing + + t = 0.0 + x = [1.0, 2.0] + p = [3.0, 4.0] + v = nothing + + # Call the method with cache - it should not error + grad_x, grad_p = Differentiation.hamiltonian_gradient(backend, FAKE_HAMILTONIAN_FIXED, t, x, p, v, cache) + # Verify the method returns a tuple of gradients + Test.@test grad_x isa AbstractVector + Test.@test length(grad_x) == length(x) + Test.@test grad_x โ‰ˆ x atol=1e-8 + Test.@test grad_p isa AbstractVector + Test.@test length(grad_p) == length(p) + Test.@test grad_p โ‰ˆ p atol=1e-8 + end + + Test.@testset "Integration: variable_gradient (no cache)" begin + # For Fixed problems (v === nothing), variable_gradient cannot be called + backend = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=false) + t = 0.0 + x = [1.0, 2.0] + p = [3.0, 4.0] + v = [5.0, 6.0] + + grad_v = Differentiation.variable_gradient(backend, FAKE_HAMILTONIAN_NONFIXED, t, x, p, v, nothing) + Test.@test grad_v isa AbstractVector + Test.@test length(grad_v) == length(v) + Test.@test grad_v โ‰ˆ v atol=1e-8 + end + + Test.@testset "Integration: variable_gradient (with cache)" begin + # For Fixed problems (v === nothing), variable_gradient returns nothing + backend = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=true) + typical_t = 0.0 + typical_x = [1.0, 2.0] + typical_p = [3.0, 4.0] + typical_v = [5.0, 6.0] + + cache = Differentiation.prepare_cache(backend, FAKE_HAMILTONIAN_NONFIXED, typical_t, typical_x, typical_p, typical_v) + Test.@test cache !== nothing + + t = 0.0 + x = [1.0, 2.0] + p = [3.0, 4.0] + v = [5.0, 6.0] + + grad_v = Differentiation.variable_gradient(backend, FAKE_HAMILTONIAN_NONFIXED, t, x, p, v, cache) + Test.@test grad_v isa AbstractVector + Test.@test length(grad_v) == length(v) + Test.@test grad_v โ‰ˆ v atol=1e-8 + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_differentiation_interface_extension() = TestDifferentiationInterfaceExtension.test_differentiation_interface_extension() diff --git a/test/suite/extensions/test_flow_callables_sciml_hamiltonian_system.jl b/test/suite/extensions/test_flow_callables_sciml_hamiltonian_system.jl new file mode 100644 index 00000000..8b5676e1 --- /dev/null +++ b/test/suite/extensions/test_flow_callables_sciml_hamiltonian_system.jl @@ -0,0 +1,249 @@ +module TestFlowCallablesSciMLHamiltonianSystem + +import Test +import CTBase.Exceptions +import CTFlows.Data: Data +import CTFlows.Common: Common +import CTFlows.Systems: Systems +import CTFlows.Flows: Flows +import CTFlows.Integrators: Integrators +import CTFlows.Differentiation: Differentiation +import CTFlows.Solutions: Solutions + +using SciMLBase: SciMLBase +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector, MVector +using ForwardDiff: ForwardDiff + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake AD backend for testing (no actual AD, just harmonic oscillator) +# ============================================================================== + +struct FakeHarmonicADBackend <: Differentiation.AbstractADBackend end + +function Differentiation.hamiltonian_gradient(backend::FakeHarmonicADBackend, h, t, x, p, v, cache) + # For H_HARMONIC: H = 0.5*(xยฒ + pยฒ) โ†’ โˆ‚H/โˆ‚x = x, โˆ‚H/โˆ‚p = p + # For H_SCALAR_ONLY: scalar-only gradients that fail with vectors + # H = xยณ/3 - log(p) โ†’ โˆ‚H/โˆ‚x = x*x, โˆ‚H/โˆ‚p = -1/p + if h === H_SCALAR_ONLY + return (x*x, -1/p) + else + return (x, p) + end +end + +function Differentiation.variable_gradient(backend::FakeHarmonicADBackend, h, t, x, p, v, cache) + # H = 0.5*vยฒ โ†’ โˆ‚H/โˆ‚v = v (for NonFixed), or 0.0 for Fixed + return v === nothing ? 0.0 : v +end + +function Differentiation.prepare_cache(backend::FakeHarmonicADBackend, h, typical_t, typical_x, typical_p, typical_v) + return nothing +end + +# ============================================================================== +# Reference systems for numerical testing +# ============================================================================== + +# Harmonic oscillator: H = 0.5*(sum(xยฒ) + sum(pยฒ)) โ†’ แบ‹ = p, แน— = -x +const H_HARMONIC = Data.Hamiltonian((x, p) -> 0.5*(sum(abs2, x) + sum(abs2, p)); + is_autonomous=true, is_variable=false) +const BACKEND = FakeHarmonicADBackend() +const HSYS = Systems.HamiltonianSystem(H_HARMONIC, BACKEND) +const INTEG = Integrators.SciML() + +# Scalar-only Hamiltonian: H = x*x + p*p (requires scalar x, p) +# This will fail if x or p are treated as vectors +const H_SCALAR_ONLY = Data.Hamiltonian((x, p) -> x*x + p*p; + is_autonomous=true, is_variable=false) +const HSYS_SCALAR = Systems.HamiltonianSystem(H_SCALAR_ONLY, BACKEND) +const ATOL = 1e-5 + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow_callables_sciml_hamiltonian_system() + Test.@testset "Flow Callables SciML HamiltonianSystem Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # INTEGRATION TESTS - HamiltonianSystem โ†’ HamiltonianFlow HamiltonianPointConfig + # ==================================================================== + + Test.@testset "HamiltonianFlow HamiltonianPointConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, 1.0, 0.0, ฯ€/2) + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test xf โ‰ˆ 0.0 atol=ATOL + Test.@test pf โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector && length(xf) == 2 + Test.@test pf isa AbstractVector && length(pf) == 2 + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "MVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "scalar complex x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, 1.0+2.0im, 0.0+0.0im, ฯ€/2) + Test.@test xf isa Complex + Test.@test pf isa Complex + Test.@test xf โ‰ˆ 0.0+0.0im atol=ATOL + Test.@test pf โ‰ˆ -(1.0+2.0im) atol=ATOL + end + + Test.@testset "complex vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + x0 = [1.0+2.0im, 0.0+0.0im] + p0 = [0.0+0.0im, 1.0+1.0im] + xf, pf = hflow(0.0, x0, p0, ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ p0 atol=ATOL + Test.@test pf โ‰ˆ -x0 atol=ATOL + end + + Test.@testset "complex SVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, SA[1.0+2.0im, 0.0+0.0im], SA[0.0+0.0im, 1.0+1.0im], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ SA[0.0+0.0im, 1.0+1.0im] atol=ATOL + Test.@test pf โ‰ˆ SA[-1.0-2.0im, 0.0+0.0im] atol=ATOL + end + + Test.@testset "matrix x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = [1.0 2.0; 3.0 4.0] + P0 = [0.0 0.0; 1.0 1.0] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test size(Xf) == (2, 2) + Test.@test size(Pf) == (2, 2) + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "complex matrix x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + P0 = [0.0+0.0im 1.0+1.0im; 2.0+2.0im 3.0+3.0im] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "ForwardDiff.Dual scalar x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + x0 = ForwardDiff.Dual(1.0, 1.0) + p0 = ForwardDiff.Dual(0.0, 0.0) + xf, pf = hflow(0.0, x0, p0, ฯ€/2) + Test.@test xf isa ForwardDiff.Dual + Test.@test pf isa ForwardDiff.Dual + Test.@test ForwardDiff.value(xf) โ‰ˆ 0.0 atol=ATOL + Test.@test ForwardDiff.value(pf) โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "ForwardDiff.Dual vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(0.0, 0.0)] + p0 = [ForwardDiff.Dual(0.0, 0.0), ForwardDiff.Dual(1.0, 0.0)] + xf, pf = hflow(0.0, x0, p0, ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test ForwardDiff.value(xf[1]) โ‰ˆ 0.0 atol=ATOL + Test.@test ForwardDiff.value(pf[1]) โ‰ˆ -1.0 atol=ATOL + end + end + + # ==================================================================== + # INTEGRATION TESTS - HamiltonianSystem โ†’ HamiltonianFlow HamiltonianTrajectoryConfig + # ==================================================================== + + Test.@testset "HamiltonianFlow HamiltonianTrajectoryConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "SVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), SA[1.0, 0.0], SA[0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + end + + # ==================================================================== + # SCALAR-ONLY TESTS - verify scalar dispatch (x*x, not sum(xยฒ)) + # ==================================================================== + + Test.@testset "Scalar-only Hamiltonian (xยณ/3 - log(p))" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS_SCALAR, INTEG) + # H = xยณ/3 - log(p) โ†’ โˆ‚H/โˆ‚x = xยฒ, โˆ‚H/โˆ‚p = -1/p + # Equations: แบ‹ = 1/p, แน— = -xยฒ + # This will fail if x or p are treated as vectors (can't do x*x or 1/p on vectors) + xf, pf = hflow(0.0, 0.5, 1.0, 0.1) + # Just verify it integrates without error and returns scalars + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test pf > 0 # p should stay positive + end + end + + # ==================================================================== + # INTEGRATION TESTS - build_system pipeline + # ==================================================================== + + Test.@testset "build_system pipeline" begin + Test.@testset "via Systems.build_system" begin + sys = Systems.build_system(H_HARMONIC, BACKEND) + flow = Flows.build_flow(sys, INTEG) + xf, pf = flow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + end + end +end + +end # module + +test_flow_callables_sciml_hamiltonian_system() = TestFlowCallablesSciMLHamiltonianSystem.test_flow_callables_sciml_hamiltonian_system() diff --git a/test/suite/extensions/test_flow_callables_sciml_hamiltonian_system_di.jl b/test/suite/extensions/test_flow_callables_sciml_hamiltonian_system_di.jl new file mode 100644 index 00000000..82c17f67 --- /dev/null +++ b/test/suite/extensions/test_flow_callables_sciml_hamiltonian_system_di.jl @@ -0,0 +1,374 @@ +""" +Full mirror of test_flow_callables_sciml_hamiltonian_system.jl using real DifferentiationInterface backend. +Tests both cached (prepare_cache=true) and uncached (prepare_cache=false) variants. +""" + +module TestFlowCallablesSciMLHamiltonianSystemDI + +import Test +import CTBase.Exceptions +import CTFlows.Data: Data +import CTFlows.Common: Common +import CTFlows.Systems: Systems +import CTFlows.Flows: Flows +import CTFlows.Integrators: Integrators +import CTFlows.Differentiation: Differentiation +import CTFlows.Solutions: Solutions + +using SciMLBase: SciMLBase +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector, MVector +using ForwardDiff: ForwardDiff +using ADTypes: ADTypes +using DifferentiationInterface: DifferentiationInterface as DI + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Reference systems for numerical testing +# ============================================================================== + +# Harmonic oscillator: H = 0.5*(sum(xยฒ) + sum(pยฒ)) โ†’ แบ‹ = p, แน— = -x +const H_HARMONIC = Data.Hamiltonian((x, p) -> 0.5*(sum(abs2, x) + sum(abs2, p)); + is_autonomous=true, is_variable=false) + +# DifferentiationInterface backends +const DI_BACKEND_CACHED = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=true) +const DI_BACKEND_UNCACHED = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=false) + +# Systems for each backend (cached and uncached) +const HSYS_CACHED = Systems.HamiltonianSystem(H_HARMONIC, DI_BACKEND_CACHED) +const HSYS_UNCACHED = Systems.HamiltonianSystem(H_HARMONIC, DI_BACKEND_UNCACHED) + +const INTEG = Integrators.SciML() +const ATOL = 1e-5 + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow_callables_sciml_hamiltonian_system_di() + Test.@testset "Flow Callables SciML HamiltonianSystem DI Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # INTEGRATION TESTS - CACHED backend + # ==================================================================== + + Test.@testset "Cached backend (prepare_cache=true)" begin + Test.@testset "HamiltonianFlow HamiltonianPointConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + xf, pf = hflow(0.0, 1.0, 0.0, ฯ€/2) + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test xf โ‰ˆ 0.0 atol=ATOL + Test.@test pf โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + xf, pf = hflow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector && length(xf) == 2 + Test.@test pf isa AbstractVector && length(pf) == 2 + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0 (N=2)" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + xf, pf = hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0 (N=nothing)" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + xf, pf = hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "MVector x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + xf, pf = hflow(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + # Test.@testset "scalar complex x0, p0" begin + # hflow = Flows.build_flow(HSYS_CACHED, INTEG) + # xf, pf = hflow(0.0, 1.0+2.0im, 0.0+0.0im, ฯ€/2) + # Test.@test xf isa Complex + # Test.@test pf isa Complex + # Test.@test xf โ‰ˆ 0.0+0.0im atol=ATOL + # Test.@test pf โ‰ˆ -(1.0+2.0im) atol=ATOL + # end + + # Test.@testset "complex vector x0, p0" begin + # hflow = Flows.build_flow(HSYS_CACHED, INTEG) + # x0 = [1.0+2.0im, 0.0+0.0im] + # p0 = [0.0+0.0im, 1.0+1.0im] + # xf, pf = hflow(0.0, x0, p0, ฯ€/2) + # Test.@test xf isa AbstractVector + # Test.@test pf isa AbstractVector + # Test.@test xf โ‰ˆ p0 atol=ATOL + # Test.@test pf โ‰ˆ -x0 atol=ATOL + # end + + # Test.@testset "complex SVector x0, p0" begin + # hflow = Flows.build_flow(HSYS_CACHED, INTEG) + # xf, pf = hflow(0.0, SA[1.0+2.0im, 0.0+0.0im], SA[0.0+0.0im, 1.0+1.0im], ฯ€/2) + # Test.@test xf isa AbstractVector + # Test.@test pf isa AbstractVector + # Test.@test xf โ‰ˆ SA[0.0+0.0im, 1.0+1.0im] atol=ATOL + # Test.@test pf โ‰ˆ SA[-1.0-2.0im, 0.0+0.0im] atol=ATOL + # end + + Test.@testset "matrix x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + X0 = [1.0 2.0; 3.0 4.0] + P0 = [0.0 0.0; 1.0 1.0] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test size(Xf) == (2, 2) + Test.@test size(Pf) == (2, 2) + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + # Test.@testset "complex matrix x0, p0" begin + # hflow = Flows.build_flow(HSYS_CACHED, INTEG) + # X0 = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + # P0 = [0.0+0.0im 1.0+1.0im; 2.0+2.0im 3.0+3.0im] + # Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + # Test.@test Xf isa AbstractMatrix + # Test.@test Pf isa AbstractMatrix + # Test.@test Xf โ‰ˆ P0 atol=ATOL + # Test.@test Pf โ‰ˆ -X0 atol=ATOL + # end + + # Test.@testset "ForwardDiff.Dual scalar x0, p0" begin + # hflow = Flows.build_flow(HSYS_CACHED, INTEG) + # x0 = ForwardDiff.Dual(1.0, 1.0) + # p0 = ForwardDiff.Dual(0.0, 0.0) + # xf, pf = hflow(0.0, x0, p0, ฯ€/2) + # Test.@test xf isa ForwardDiff.Dual + # Test.@test pf isa ForwardDiff.Dual + # Test.@test ForwardDiff.value(xf) โ‰ˆ 0.0 atol=ATOL + # Test.@test ForwardDiff.value(pf) โ‰ˆ -1.0 atol=ATOL + # end + + # Test.@testset "ForwardDiff.Dual vector x0, p0" begin + # hflow = Flows.build_flow(HSYS_CACHED, INTEG) + # x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(0.0, 0.0)] + # p0 = [ForwardDiff.Dual(0.0, 0.0), ForwardDiff.Dual(1.0, 0.0)] + # xf, pf = hflow(0.0, x0, p0, ฯ€/2) + # Test.@test xf isa AbstractVector + # Test.@test pf isa AbstractVector + # Test.@test ForwardDiff.value(xf[1]) โ‰ˆ 0.0 atol=ATOL + # Test.@test ForwardDiff.value(pf[1]) โ‰ˆ -1.0 atol=ATOL + # end + end + + Test.@testset "HamiltonianFlow HamiltonianTrajectoryConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "SVector x0, p0" begin + hflow = Flows.build_flow(HSYS_CACHED, INTEG) + sol = hflow((0.0, ฯ€/2), SA[1.0, 0.0], SA[0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + end + + Test.@testset "build_system pipeline" begin + Test.@testset "via Systems.build_system" begin + sys = Systems.build_system(H_HARMONIC, DI_BACKEND_CACHED) + flow = Flows.build_flow(sys, INTEG) + xf, pf = flow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + end + end + + # ==================================================================== + # INTEGRATION TESTS - UN-CACHED backend + # ==================================================================== + + Test.@testset "Uncached backend (prepare_cache=false)" begin + Test.@testset "HamiltonianFlow HamiltonianPointConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + xf, pf = hflow(0.0, 1.0, 0.0, ฯ€/2) + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test xf โ‰ˆ 0.0 atol=ATOL + Test.@test pf โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + xf, pf = hflow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector && length(xf) == 2 + Test.@test pf isa AbstractVector && length(pf) == 2 + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0 (N=2)" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + xf, pf = hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0 (N=nothing)" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + xf, pf = hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "MVector x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + xf, pf = hflow(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + # Test.@testset "scalar complex x0, p0" begin + # hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + # xf, pf = hflow(0.0, 1.0+2.0im, 0.0+0.0im, ฯ€/2) + # Test.@test xf isa Complex + # Test.@test pf isa Complex + # Test.@test xf โ‰ˆ 0.0+0.0im atol=ATOL + # Test.@test pf โ‰ˆ -(1.0+2.0im) atol=ATOL + # end + + # Test.@testset "complex vector x0, p0" begin + # hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + # x0 = [1.0+2.0im, 0.0+0.0im] + # p0 = [0.0+0.0im, 1.0+1.0im] + # xf, pf = hflow(0.0, x0, p0, ฯ€/2) + # Test.@test xf isa AbstractVector + # Test.@test pf isa AbstractVector + # Test.@test xf โ‰ˆ p0 atol=ATOL + # Test.@test pf โ‰ˆ -x0 atol=ATOL + # end + + # Test.@testset "complex SVector x0, p0" begin + # hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + # xf, pf = hflow(0.0, SA[1.0+2.0im, 0.0+0.0im], SA[0.0+0.0im, 1.0+1.0im], ฯ€/2) + # Test.@test xf isa AbstractVector + # Test.@test pf isa AbstractVector + # Test.@test xf โ‰ˆ SA[0.0+0.0im, 1.0+1.0im] atol=ATOL + # Test.@test pf โ‰ˆ SA[-1.0-2.0im, 0.0+0.0im] atol=ATOL + # end + + Test.@testset "matrix x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + X0 = [1.0 2.0; 3.0 4.0] + P0 = [0.0 0.0; 1.0 1.0] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test size(Xf) == (2, 2) + Test.@test size(Pf) == (2, 2) + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + # Test.@testset "complex matrix x0, p0" begin + # hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + # X0 = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + # P0 = [0.0+0.0im 1.0+1.0im; 2.0+2.0im 3.0+3.0im] + # Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + # Test.@test Xf isa AbstractMatrix + # Test.@test Pf isa AbstractMatrix + # Test.@test Xf โ‰ˆ P0 atol=ATOL + # Test.@test Pf โ‰ˆ -X0 atol=ATOL + # end + + # Test.@testset "ForwardDiff.Dual scalar x0, p0" begin + # hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + # x0 = ForwardDiff.Dual(1.0, 1.0) + # p0 = ForwardDiff.Dual(0.0, 0.0) + # xf, pf = hflow(0.0, x0, p0, ฯ€/2) + # Test.@test xf isa ForwardDiff.Dual + # Test.@test pf isa ForwardDiff.Dual + # Test.@test ForwardDiff.value(xf) โ‰ˆ 0.0 atol=ATOL + # Test.@test ForwardDiff.value(pf) โ‰ˆ -1.0 atol=ATOL + # end + + # Test.@testset "ForwardDiff.Dual vector x0, p0" begin + # hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + # x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(0.0, 0.0)] + # p0 = [ForwardDiff.Dual(0.0, 0.0), ForwardDiff.Dual(1.0, 0.0)] + # xf, pf = hflow(0.0, x0, p0, ฯ€/2) + # Test.@test xf isa AbstractVector + # Test.@test pf isa AbstractVector + # Test.@test ForwardDiff.value(xf[1]) โ‰ˆ 0.0 atol=ATOL + # Test.@test ForwardDiff.value(pf[1]) โ‰ˆ -1.0 atol=ATOL + # end + end + + Test.@testset "HamiltonianFlow HamiltonianTrajectoryConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "SVector x0, p0" begin + hflow = Flows.build_flow(HSYS_UNCACHED, INTEG) + sol = hflow((0.0, ฯ€/2), SA[1.0, 0.0], SA[0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + end + + Test.@testset "build_system pipeline" begin + Test.@testset "via Systems.build_system" begin + sys = Systems.build_system(H_HARMONIC, DI_BACKEND_UNCACHED) + flow = Flows.build_flow(sys, INTEG) + xf, pf = flow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + end + end + end +end + +end # module + +test_flow_callables_sciml_hamiltonian_system_di() = TestFlowCallablesSciMLHamiltonianSystemDI.test_flow_callables_sciml_hamiltonian_system_di() diff --git a/test/suite/extensions/test_flow_callables_sciml_hamiltonian_vector_field.jl b/test/suite/extensions/test_flow_callables_sciml_hamiltonian_vector_field.jl new file mode 100644 index 00000000..926611e7 --- /dev/null +++ b/test/suite/extensions/test_flow_callables_sciml_hamiltonian_vector_field.jl @@ -0,0 +1,236 @@ +module TestFlowCallablesSciMLHamiltonianVectorField + +import Test +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Solutions +import CTFlows.Common +import CTFlows.Data + +using SciMLBase: SciMLBase +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector, MVector, SMatrix +using ForwardDiff: ForwardDiff + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Reference systems for numerical testing +# ============================================================================== + +# For HamiltonianFlow: harmonic oscillator (x' = p, p' = -x) +# Solution: x(t) = x0 cos(t) + p0 sin(t), p(t) = -x0 sin(t) + p0 cos(t) +const HVF_HARMONIC = Data.HamiltonianVectorField((x, p) -> (p, -x); is_autonomous=true, is_variable=false) +const HSYS = Systems.HamiltonianVectorFieldSystem(HVF_HARMONIC) # lazy (N inferred at build_problem time) +const ATOL = 1e-5 + +# InPlace variants (same dynamics, different function signature) +const HVF_HARMONIC_IP = Data.HamiltonianVectorField((dx, dp, x, p) -> (dx .= p; dp .= -x); is_autonomous=true, is_variable=false) +const HSYS_IP = Systems.HamiltonianVectorFieldSystem(HVF_HARMONIC_IP) + +const INTEG = Integrators.SciML() + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow_callables_sciml_hamiltonian_vector_field() + Test.@testset "Flow Callables SciML HamiltonianVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # INTEGRATION TESTS - HamiltonianFlow HamiltonianPointConfig + # ==================================================================== + + Test.@testset "HamiltonianFlow HamiltonianPointConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, 1.0, 0.0, ฯ€/2) + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test xf โ‰ˆ 0.0 atol=ATOL + Test.@test pf โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "scalar complex x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, 1.0+2.0im, 0.0+0.0im, ฯ€/2) + Test.@test xf isa Complex + Test.@test pf isa Complex + Test.@test xf โ‰ˆ 0.0+0.0im atol=ATOL + Test.@test pf โ‰ˆ -(1.0+2.0im) atol=ATOL + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, [1.0, 0.0], [0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector && length(xf) == 2 + Test.@test pf isa AbstractVector && length(pf) == 2 + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "MVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector complex x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + xf, pf = hflow(0.0, SA[1.0+2.0im, 0.0+0.0im], SA[0.0+0.0im, 1.0+1.0im], ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ SA[0.0+0.0im, 1.0+1.0im] atol=ATOL + Test.@test pf โ‰ˆ SA[-1.0-2.0im, 0.0+0.0im] atol=ATOL + end + + Test.@testset "matrix x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = [1.0 2.0; 3.0 4.0] + P0 = [0.0 0.0; 1.0 1.0] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test size(Xf) == (2, 2) + Test.@test size(Pf) == (2, 2) + # For harmonic oscillator: x(t) = x0 cos(t) + p0 sin(t), p(t) = -x0 sin(t) + p0 cos(t) + # At t=ฯ€/2: x(ฯ€/2) = p0, p(ฯ€/2) = -x0 + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "SMatrix x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = SMatrix{2,2}(1.0, 3.0, 2.0, 4.0) # column-major: [1 2; 3 4] + P0 = SMatrix{2,2}(0.0, 1.0, 0.0, 1.0) # column-major: [0 0; 1 1] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + # vcat(SMatrix, SMatrix) โ†’ Matrix, so ODE returns AbstractMatrix + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "complex vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + # x' = p, p' = -x โ†’ at t=ฯ€/2: xf = p0, pf = -x0 + x0 = [1.0+2.0im, 0.0+0.0im] + p0 = [0.0+0.0im, 1.0+1.0im] + xf, pf = hflow(0.0, x0, p0, ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test xf โ‰ˆ p0 atol=ATOL + Test.@test pf โ‰ˆ -x0 atol=ATOL + end + + Test.@testset "complex matrix x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + # x' = p, p' = -x โ†’ at t=ฯ€/2: Xf = P0, Pf = -X0 + X0 = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + P0 = [0.0+0.0im 1.0+1.0im; 2.0+2.0im 3.0+3.0im] + Xf, Pf = hflow(0.0, X0, P0, ฯ€/2) + Test.@test Xf isa AbstractMatrix + Test.@test Pf isa AbstractMatrix + Test.@test Xf โ‰ˆ P0 atol=ATOL + Test.@test Pf โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "ForwardDiff.Dual scalar x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + x0 = ForwardDiff.Dual(1.0, 1.0) + p0 = ForwardDiff.Dual(0.0, 0.0) + xf, pf = hflow(0.0, x0, p0, ฯ€/2) + Test.@test xf isa ForwardDiff.Dual + Test.@test pf isa ForwardDiff.Dual + Test.@test ForwardDiff.value(xf) โ‰ˆ 0.0 atol=ATOL + Test.@test ForwardDiff.value(pf) โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "ForwardDiff.Dual vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(0.0, 0.0)] + p0 = [ForwardDiff.Dual(0.0, 0.0), ForwardDiff.Dual(1.0, 0.0)] + xf, pf = hflow(0.0, x0, p0, ฯ€/2) + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test ForwardDiff.value(xf[1]) โ‰ˆ 0.0 atol=ATOL + Test.@test ForwardDiff.value(pf[1]) โ‰ˆ -1.0 atol=ATOL + end + end + + # ==================================================================== + # INTEGRATION TESTS - HamiltonianFlow HamiltonianTrajectoryConfig + # ==================================================================== + + Test.@testset "HamiltonianFlow HamiltonianTrajectoryConfig" begin + Test.@testset "scalar x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "vector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "SVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), SA[1.0, 0.0], SA[0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "MVector x0, p0" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0)) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + end + end + + # ==================================================================== + # INTEGRATION TESTS - InPlace HamiltonianFlow + # ==================================================================== + + Test.@testset "InPlace HamiltonianFlow" begin + Test.@testset "IP HVF + Vector u0 (no warning)" begin + hflow = Flows.build_flow(HSYS_IP, INTEG) + xf, pf = hflow(0.0, 1.0, 0.0, ฯ€/2) + Test.@test xf โ‰ˆ 0.0 atol=ATOL + Test.@test pf โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "IP HVF + SVector u0 (warns)" begin + hflow = Flows.build_flow(HSYS_IP, INTEG) + xf, pf = Test.@test_logs (:warn, r"InPlace HamiltonianVectorField") hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], ฯ€/2) + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "IP HVF + MVector u0 (no warning)" begin + hflow = Flows.build_flow(HSYS_IP, INTEG) + xf, pf = hflow(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), ฯ€/2) + Test.@test xf โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test pf โ‰ˆ [-1.0, 0.0] atol=ATOL + end + end + end +end + +end # module + +test_flow_callables_sciml_hamiltonian_vector_field() = TestFlowCallablesSciMLHamiltonianVectorField.test_flow_callables_sciml_hamiltonian_vector_field() diff --git a/test/suite/extensions/test_flow_callables_sciml_vector_field.jl b/test/suite/extensions/test_flow_callables_sciml_vector_field.jl new file mode 100644 index 00000000..d09fd516 --- /dev/null +++ b/test/suite/extensions/test_flow_callables_sciml_vector_field.jl @@ -0,0 +1,180 @@ +module TestFlowCallablesSciMLVectorField + +import Test +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Solutions +import CTFlows.Common +import CTFlows.Data + +using SciMLBase: SciMLBase +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector, MVector +using ForwardDiff: ForwardDiff + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Reference systems for numerical testing +# ============================================================================== + +# For StateFlow: exponential decay x' = -x -> x(t) = x0 * exp(-t) +const VF_DECAY = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) +const SYS_DECAY = Systems.VectorFieldSystem(VF_DECAY) +const VF_DECAY_IP = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) +const SYS_DECAY_IP = Systems.VectorFieldSystem(VF_DECAY_IP) +const INTEG = Integrators.SciML() +const ATOL = 1e-5 + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow_callables_sciml_vector_field() + Test.@testset "Flow Callables SciML VectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # INTEGRATION TESTS - StateFlow StatePointConfig + # ==================================================================== + + Test.@testset "StateFlow StatePointConfig" begin + flow = Flows.build_flow(SYS_DECAY, INTEG) + + Test.@testset "scalar Real x0" begin + xf = flow(0.0, 1.0, 1.0) + Test.@test xf isa Real + Test.@test xf โ‰ˆ exp(-1.0) atol=ATOL + end + + Test.@testset "vector x0" begin + xf = flow(0.0, [1.0, 2.0], 1.0) + Test.@test xf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test xf โ‰ˆ [exp(-1.0), 2*exp(-1.0)] atol=ATOL + end + + Test.@testset "SVector x0" begin + xf = flow(0.0, SA[1.0, 2.0], 1.0) + Test.@test xf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test xf โ‰ˆ [exp(-1.0), 2*exp(-1.0)] atol=ATOL + end + + Test.@testset "MVector x0" begin + xf = flow(0.0, MVector{2}(1.0, 2.0), 1.0) + Test.@test xf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test xf โ‰ˆ [exp(-1.0), 2*exp(-1.0)] atol=ATOL + end + + Test.@testset "scalar complex x0" begin + xf = flow(0.0, 1.0+2.0im, 1.0) + Test.@test xf isa Complex + Test.@test xf โ‰ˆ (1.0+2.0im) * exp(-1.0) atol=ATOL + end + + Test.@testset "complex vector x0" begin + xf = flow(0.0, [1.0+2.0im, 3.0+4.0im], 1.0) + Test.@test xf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test xf โ‰ˆ [1.0+2.0im, 3.0+4.0im] * exp(-1.0) atol=ATOL + end + + Test.@testset "SVector complex x0" begin + xf = flow(0.0, SA[1.0+2.0im, 3.0+4.0im], 1.0) + Test.@test xf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test xf โ‰ˆ [exp(-1.0)*(1.0+2.0im), exp(-1.0)*(3.0+4.0im)] atol=ATOL + end + + Test.@testset "ForwardDiff.Dual scalar x0" begin + x0 = ForwardDiff.Dual(1.0, 1.0) + xf = flow(0.0, x0, 1.0) + Test.@test xf isa ForwardDiff.Dual + Test.@test ForwardDiff.value(xf) โ‰ˆ exp(-1.0) atol=ATOL + end + + Test.@testset "ForwardDiff.Dual vector x0" begin + x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(2.0, 0.0)] + xf = flow(0.0, x0, 1.0) + Test.@test xf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test ForwardDiff.value(xf[1]) โ‰ˆ exp(-1.0) atol=ATOL + Test.@test ForwardDiff.value(xf[2]) โ‰ˆ 2*exp(-1.0) atol=ATOL + end + + Test.@testset "matrix x0" begin + X0 = [1.0 2.0; 3.0 4.0] + Xf = flow(0.0, X0, 1.0) + Test.@test Xf isa AbstractMatrix + Test.@test size(Xf) == (2, 2) + Test.@test Xf โ‰ˆ X0 * exp(-1.0) atol=ATOL + end + + Test.@testset "complex matrix x0" begin + X0 = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + Xf = flow(0.0, X0, 1.0) + Test.@test Xf isa AbstractMatrix + Test.@test size(Xf) == (2, 2) + Test.@test Xf โ‰ˆ X0 * exp(-1.0) atol=ATOL + end + end + + # ==================================================================== + # INTEGRATION TESTS - StateFlow StateTrajectoryConfig + # ==================================================================== + + Test.@testset "StateFlow StateTrajectoryConfig" begin + flow = Flows.build_flow(SYS_DECAY, INTEG) + + Test.@testset "vector x0" begin + sol = flow((0.0, 1.0), [1.0, 2.0]) + Test.@test sol isa Solutions.VectorFieldSolution + end + + Test.@testset "SVector x0" begin + sol = flow((0.0, 1.0), SA[1.0, 2.0]) + Test.@test sol isa Solutions.VectorFieldSolution + end + + Test.@testset "MVector x0" begin + sol = flow((0.0, 1.0), MVector{2}(1.0, 2.0)) + Test.@test sol isa Solutions.VectorFieldSolution + end + + Test.@testset "matrix x0" begin + sol = flow((0.0, 1.0), [1.0 2.0; 3.0 4.0]) + Test.@test sol isa Solutions.VectorFieldSolution + end + end + + # ==================================================================== + # INTEGRATION TESTS - InPlace StateFlow + # ==================================================================== + + Test.@testset "InPlace StateFlow" begin + flow = Flows.build_flow(SYS_DECAY_IP, INTEG) + + Test.@testset "IP VF + Vector u0 (no warning)" begin + xf = flow(0.0, [1.0, 2.0], 1.0) + Test.@test xf โ‰ˆ [exp(-1.0), 2*exp(-1.0)] atol=ATOL + end + + Test.@testset "IP VF + SVector u0 (warns)" begin + xf = Test.@test_logs (:warn, r"InPlace VectorField") flow(0.0, SA[1.0, 2.0], 1.0) + Test.@test xf โ‰ˆ [exp(-1.0), 2*exp(-1.0)] atol=ATOL + end + + Test.@testset "IP VF + MVector u0 (no warning)" begin + xf = flow(0.0, MVector{2}(1.0, 2.0), 1.0) + Test.@test xf โ‰ˆ [exp(-1.0), 2*exp(-1.0)] atol=ATOL + end + end + end +end + +end # module + +test_flow_callables_sciml_vector_field() = TestFlowCallablesSciMLVectorField.test_flow_callables_sciml_vector_field() diff --git a/test/suite/extensions/test_forwarddiff_extension.jl b/test/suite/extensions/test_forwarddiff_extension.jl new file mode 100644 index 00000000..c71a6ad6 --- /dev/null +++ b/test/suite/extensions/test_forwarddiff_extension.jl @@ -0,0 +1,186 @@ +module TestForwardDiffExtension + +import Test +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Data: Data +import CTFlows.Systems: Systems +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions +import CTFlows.Flows: Flows +import CTSolvers.Strategies: Strategies + +using SciMLBase: SciMLBase, ODEProblem +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using DiffEqBase: DiffEqBase +using ForwardDiff: ForwardDiff + +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsForwardDiff = Base.get_extension(CTFlows, :CTFlowsForwardDiff) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_forwarddiff_extension() + Test.@testset "ForwardDiff Extension Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # Extension availability check + # ==================================================================== + + Test.@testset "Extension availability" begin + Test.@testset "CTFlowsSciML is loaded" begin + Test.@test !isnothing(CTFlowsSciML) + end + + Test.@testset "CTFlowsForwardDiff availability" begin + if isnothing(CTFlowsForwardDiff) + Test.@test_skip "ForwardDiff extension not loaded (ForwardDiff not installed)" + else + Test.@test CTFlowsForwardDiff isa Module + end + end + end + + # Skip ForwardDiff-specific tests if extension is not loaded + if isnothing(CTFlowsForwardDiff) + return + end + + # ==================================================================== + # UNIT TESTS - Common fallbacks + # ==================================================================== + + Test.@testset "Common fallbacks" begin + Test.@testset "deepvalue(x::Real) is identity" begin + Test.@test Common.deepvalue(1.0) === 1.0 + Test.@test Common.deepvalue(2.5) === 2.5 + end + + Test.@testset "real_norm(u::Real, t) is abs" begin + Test.@test Common.real_norm(3.0, 0.0) === 3.0 + Test.@test Common.real_norm(-5.0, 0.0) === 5.0 + end + end + + # ==================================================================== + # UNIT TESTS - deepvalue extraction (ForwardDiff) + # ==================================================================== + + Test.@testset "deepvalue extraction" begin + Test.@testset "order 1 โ€” single Dual" begin + d1 = ForwardDiff.Dual{:Tag1}(3.0, 1.0) + Test.@test Common.deepvalue(d1) === 3.0 + + d1b = ForwardDiff.Dual{:Tag1}(5.5, 2.0, 3.0) + Test.@test Common.deepvalue(d1b) === 5.5 + end + + Test.@testset "order 2 โ€” nested Dual" begin + d1 = ForwardDiff.Dual{:Tag1}(3.0, 1.0) + d2 = ForwardDiff.Dual{:Tag2}(d1, d1) + Test.@test Common.deepvalue(d2) === 3.0 + end + end + + # ==================================================================== + # UNIT TESTS - real_norm ignores dual parts (ForwardDiff) + # ==================================================================== + + Test.@testset "real_norm ignores dual parts" begin + u_real = [1.0, 2.0, 3.0] + u_dual = ForwardDiff.Dual{:T}.(u_real, ones(3)) + + # The norm must be identical regardless of dual parts + norm_real = Common.real_norm(u_real, 0.0) + norm_dual = Common.real_norm(u_dual, 0.0) + Test.@test norm_real โ‰ˆ norm_dual + end + + # ==================================================================== + # INTEGRATION TESTS - Grid invariance + # ==================================================================== + + Test.@testset "grids differ WITHOUT real_norm (baseline)" begin + f!(du, u, p, t) = (du .= u) # แบ‹ = x + u0_real = [1.0] + + # Integration with real numbers + prob_real = ODEProblem(f!, u0_real, (0.0, 1.0), nothing) + sol_real = SciMLBase.solve(prob_real, Tsit5(); reltol=1e-8, abstol=1e-8, dense=false, save_everystep=true) + + # Integration with Dual (Jacobian w.r.t. u0) using DiffEqBase.ODE_DEFAULT_NORM + function integrate_dual_default(x0) + prob = ODEProblem(f!, x0, (0.0, 1.0), nothing) + return SciMLBase.solve(prob, Tsit5(); reltol=1e-8, abstol=1e-8, dense=false, + internalnorm=DiffEqBase.ODE_DEFAULT_NORM, save_everystep=true) + end + u0_dual = ForwardDiff.Dual{:T}.([1.0], [1.0]) + sol_dual = integrate_dual_default(u0_dual) + + # The grids MUST be different with DiffEqBase.ODE_DEFAULT_NORM + t_real = sol_real.t + t_dual = ForwardDiff.value.(sol_dual.t) + Test.@test t_real โ‰  t_dual + end + + Test.@testset "grids identical WITH real_norm (objective)" begin + f!(du, u, p, t) = (du .= u) + u0_real = [1.0] + + prob_real = ODEProblem(f!, u0_real, (0.0, 1.0), nothing) + sol_real = SciMLBase.solve(prob_real, Tsit5(); + reltol=1e-8, abstol=1e-8, dense=false, + internalnorm=Common.real_norm, save_everystep=true) + + function integrate_dual_with_norm(x0) + prob = ODEProblem(f!, x0, (0.0, 1.0), nothing) + return SciMLBase.solve(prob, Tsit5(); + reltol=1e-8, abstol=1e-8, dense=false, + internalnorm=Common.real_norm, save_everystep=true) + end + u0_dual = ForwardDiff.Dual{:T}.([1.0], [1.0]) + sol_dual = integrate_dual_with_norm(u0_dual) + + t_real = sol_real.t + t_dual = ForwardDiff.value.(sol_dual.t) + Test.@test t_real == t_dual + end + + # ==================================================================== + # UNIT TESTS - Default option verification + # ==================================================================== + + Test.@testset "real_norm is default internalnorm" begin + integ = Integrators.build_integrator() + opts = Strategies.options_dict(integ) + Test.@test haskey(opts, :internalnorm) + Test.@test opts[:internalnorm] === Common.real_norm + end + + # ==================================================================== + # INTEGRATION TESTS - Flow API grid invariance + # ==================================================================== + + Test.@testset "Flow API grid invariance" begin + # Simple ODE: แบ‹ = x + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + flow = Flows.Flow(vf; save_everystep=true) + + # Integration with real numbers + result_real = flow((0.0, 1.0), [1.0]) + t_real = Integrators.times(result_real) + + # Integration with Dual (Jacobian w.r.t. initial condition) + result_dual = flow((0.0, 1.0), ForwardDiff.Dual{:T}.([1.0], [1.0])) + t_dual = ForwardDiff.value.(Integrators.times(result_dual)) + + # Grids must be identical with real_norm (default) + Test.@test t_real == t_dual + end + end +end + +end # module + +test_forwarddiff_extension() = TestForwardDiffExtension.test_forwarddiff_extension() diff --git a/test/suite/extensions/test_plots_extension.jl b/test/suite/extensions/test_plots_extension.jl new file mode 100644 index 00000000..d236e765 --- /dev/null +++ b/test/suite/extensions/test_plots_extension.jl @@ -0,0 +1,263 @@ +module TestPlotsExtension + +import Test +import CTFlows: CTFlows +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions + +# Get extension to access plotting functions +using Plots +const CTFlowsPlots = Base.get_extension(CTFlows, :CTFlowsPlots) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing +# ============================================================================== + +""" +Fake integration result for testing plotting. +""" +struct FakePlotIntegrationResult{T} <: Integrators.AbstractIntegrationResult + t::Vector{Float64} + u::Vector{T} +end + +Integrators.times(r::FakePlotIntegrationResult) = r.t + +function Integrators.evaluate_at(r::FakePlotIntegrationResult, t::Real) + idx = findfirst(==(t), r.t) + return isnothing(idx) ? r.u[1] : r.u[idx] +end + +# Make our FakePlotIntegrationResult callable so sol.(ts) works +function (r::FakePlotIntegrationResult)(t::Real) + return Integrators.evaluate_at(r, t) +end + +""" +Fake integration result for Hamiltonian solution testing. +""" +struct FakeHamiltonianPlotResult <: Integrators.AbstractIntegrationResult + t::Vector{Float64} + u::Vector{Vector{Float64}} +end + +Integrators.times(r::FakeHamiltonianPlotResult) = r.t + +function Integrators.evaluate_at(r::FakeHamiltonianPlotResult, t::Real) + idx = findfirst(==(t), r.t) + return isnothing(idx) ? r.u[1] : r.u[idx] +end + +# Make our FakeHamiltonianPlotResult callable +function (r::FakeHamiltonianPlotResult)(t::Real) + return Integrators.evaluate_at(r, t) +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_plots_extension() + Test.@testset "Plots Extension" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Extension Loading + # ==================================================================== + + Test.@testset "Extension Loading" begin + Test.@testset "extension is loaded" begin + Test.@test !isnothing(CTFlowsPlots) + end + + Test.@testset "extension is a Module" begin + Test.@test CTFlowsPlots isa Module + end + end + + # ==================================================================== + # UNIT TESTS - Plot Functions Exist + # ==================================================================== + + Test.@testset "Plot Functions" begin + Test.@testset "plot method exists" begin + Test.@test isdefined(Plots, :plot) + end + + Test.@testset "plot! method exists" begin + Test.@test isdefined(Plots, :plot!) + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helper _sol_to_arrays + # ==================================================================== + + Test.@testset "Internal Helper _sol_to_arrays" begin + Test.@testset "vector state" begin + fake_result = FakePlotIntegrationResult([0.0, 1.0], [[1.0, 2.0], [3.0, 4.0]]) + sol = Solutions.VectorFieldSolution(fake_result) + + ts, states = CTFlowsPlots._sol_to_arrays(sol) + Test.@test ts == [0.0, 1.0] + Test.@test states == [1.0 2.0; 3.0 4.0] + end + + Test.@testset "scalar state" begin + fake_result = FakePlotIntegrationResult([0.0, 1.0], [1.0, 3.0]) + sol = Solutions.VectorFieldSolution(fake_result) + + ts, states = CTFlowsPlots._sol_to_arrays(sol) + Test.@test ts == [0.0, 1.0] + Test.@test states == reshape([1.0, 3.0], 2, 1) + end + + Test.@testset "uses semantic accessors" begin + # Behavioral test: verify _sol_to_arrays works correctly + # The implementation detail (using state) is verified by code review + fake_result = FakePlotIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(fake_result) + + ts, states = CTFlowsPlots._sol_to_arrays(sol) + Test.@test length(ts) == 3 + Test.@test size(states) == (3, 1) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Plotting VectorFieldSolution + # ==================================================================== + + Test.@testset "VectorFieldSolution Plotting" begin + Test.@testset "plot accepts VectorFieldSolution" begin + fake_result = FakePlotIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(fake_result) + + # Test that plot accepts the solution (may not actually plot without display) + # We just verify it doesn't throw an error + Test.@test_nowarn Plots.plot(sol, legend=false) + end + + Test.@testset "plot! accepts VectorFieldSolution" begin + fake_result = FakePlotIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(fake_result) + + # Create a plot and plot! onto it + p = Plots.plot([1, 2, 3]) + Test.@test_nowarn Plots.plot!(p, sol) + end + + Test.@testset "plot!(p, sol) accepts VectorFieldSolution" begin + fake_result = FakePlotIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(fake_result) + + # Create a plot and plot! onto it with explicit plot object + p = Plots.plot([1, 2, 3]) + Test.@test_nowarn Plots.plot!(p, sol) + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helper _ham_sol_to_arrays + # ==================================================================== + + Test.@testset "Internal Helper _ham_sol_to_arrays" begin + Test.@testset "scalar state and costate" begin + fake_result = FakeHamiltonianPlotResult( + [0.0, 1.0], + [ + [1.0, 5.0], + [3.0, 7.0], + ], + ) + x0 = 1.0 # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, fake_result) + + ts, states, costates = CTFlowsPlots._ham_sol_to_arrays(sol) + Test.@test ts == [0.0, 1.0] + Test.@test size(states) == (2, 1) + Test.@test size(costates) == (2, 1) + Test.@test states[:, 1] == [1.0, 3.0] + Test.@test costates[:, 1] == [5.0, 7.0] + end + + Test.@testset "vector state and costate" begin + fake_result = FakeHamiltonianPlotResult( + [0.0, 1.0], + [ + [1.0, 2.0, 5.0, 6.0], + [3.0, 4.0, 7.0, 8.0], + ], + ) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, fake_result) + + ts, states, costates = CTFlowsPlots._ham_sol_to_arrays(sol) + Test.@test ts == [0.0, 1.0] + Test.@test size(states) == (2, 2) + Test.@test size(costates) == (2, 2) + Test.@test states == [1.0 2.0; 3.0 4.0] + Test.@test costates == [5.0 6.0; 7.0 8.0] + end + end + + # ==================================================================== + # INTEGRATION TESTS - Plotting HamiltonianVectorFieldSolution + # ==================================================================== + + Test.@testset "HamiltonianVectorFieldSolution Plotting" begin + Test.@testset "plot accepts HamiltonianVectorFieldSolution" begin + fake_result = FakeHamiltonianPlotResult( + [0.0, 0.5, 1.0], + [ + [1.0, 2.0], + [0.5, 1.0], + [0.25, 0.5], + ], + ) + x0 = 1.0 # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, fake_result) + + Test.@test_nowarn Plots.plot(sol, legend=false) + end + + Test.@testset "plot! accepts HamiltonianVectorFieldSolution" begin + fake_result = FakeHamiltonianPlotResult( + [0.0, 0.5, 1.0], + [ + [1.0, 2.0], + [0.5, 1.0], + [0.25, 0.5], + ], + ) + x0 = 1.0 # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, fake_result) + + p = Plots.plot([1, 2, 3]) + Test.@test_nowarn Plots.plot!(p, sol) + end + + Test.@testset "plot!(p, sol) accepts HamiltonianVectorFieldSolution" begin + fake_result = FakeHamiltonianPlotResult( + [0.0, 0.5, 1.0], + [ + [1.0, 2.0], + [0.5, 1.0], + [0.25, 0.5], + ], + ) + x0 = 1.0 # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, fake_result) + + p = Plots.plot([1, 2, 3]) + Test.@test_nowarn Plots.plot!(p, sol) + end + end + end +end + +end # module + +test_plots_extension() = TestPlotsExtension.test_plots_extension() \ No newline at end of file diff --git a/test/suite/extensions/test_sciml_extension.jl b/test/suite/extensions/test_sciml_extension.jl new file mode 100644 index 00000000..0fdb02ec --- /dev/null +++ b/test/suite/extensions/test_sciml_extension.jl @@ -0,0 +1,584 @@ +module TestSciMLExtension + +import Test +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Configs: Configs +import CTFlows.Data: Data +import CTFlows.Systems: Systems +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions +import CTSolvers.Strategies: Strategies +import CTSolvers.Options: Options + +# Fake tag type for testing stub behavior +struct FakeTag <: Common.AbstractTag end + +# Get extension to access SciML integrator +using SciMLBase: SciMLBase, ODEProblem +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +import StaticArrays: SA +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_sciml_extension() + Test.@testset "SciML Extension" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Extension Loading + # ==================================================================== + + Test.@testset "Extension Loading" begin + Test.@testset "extension is loaded" begin + Test.@test !isnothing(CTFlowsSciML) + end + + Test.@testset "extension is a Module" begin + Test.@test CTFlowsSciML isa Module + end + + Test.@testset "Tsit5 extension is loaded" begin + Test.@test !isnothing(CTFlowsOrdinaryDiffEqTsit5) + end + end + + # ==================================================================== + # UNIT TESTS - Stub Behavior + # ==================================================================== + + Test.@testset "Stub Behavior" begin + Test.@testset "__default_sciml_algorithm returns missing for fake tag" begin + result = Integrators.__default_sciml_algorithm(FakeTag) + Test.@test result === missing + end + + Test.@testset "__default_sciml_algorithm returns Tsit5 for Tsit5Tag" begin + result = Integrators.__default_sciml_algorithm(Integrators.Tsit5Tag) + Test.@test result isa SciMLBase.AbstractDEAlgorithm + Test.@test result == Tsit5() + end + end + + # ==================================================================== + # UNIT TESTS - Metadata + # ==================================================================== + + Test.@testset "Metadata" begin + meta = Strategies.metadata(Integrators.SciML) + + Test.@test meta isa Strategies.StrategyMetadata + Test.@test length(meta) > 0 + + # Test that key options are defined + Test.@test :alg in keys(meta) + Test.@test :reltol in keys(meta) + Test.@test :abstol in keys(meta) + Test.@test :maxiters in keys(meta) + + # Test option types + Test.@test Options.type(meta[:alg]) == SciMLBase.AbstractDEAlgorithm + Test.@test Options.type(meta[:reltol]) == Real + Test.@test Options.type(meta[:abstol]) == Real + Test.@test Options.type(meta[:maxiters]) == Integer + + # Test default values exist + Test.@test Options.default(meta[:alg]) isa SciMLBase.AbstractDEAlgorithm + Test.@test Options.default(meta[:reltol]) isa Real + Test.@test Options.default(meta[:abstol]) isa Real + end + + # ==================================================================== + # UNIT TESTS - Constructor + # ==================================================================== + + Test.@testset "Constructor" begin + # Default constructor + integ = Integrators.SciML() + Test.@test integ isa Integrators.SciML + Test.@test integ isa Integrators.AbstractIntegrator + + # Constructor with options + integ_custom = Integrators.SciML(reltol=1e-6, abstol=1e-8) + Test.@test integ_custom isa Integrators.SciML + + # Test Strategies.options() returns StrategyOptions + opts = Strategies.options(integ) + Test.@test opts isa Strategies.StrategyOptions + + opts_custom = Strategies.options(integ_custom) + Test.@test opts_custom isa Strategies.StrategyOptions + end + + # ==================================================================== + # UNIT TESTS - Options Extraction + # ==================================================================== + + Test.@testset "Options Extraction" begin + integ = Integrators.SciML(reltol=1e-8, abstol=1e-10, maxiters=1000) + opts = Strategies.options(integ) + + # Extract raw options (returns NamedTuple) + raw_opts = Options.extract_raw_options(Strategies._raw_options(opts)) + Test.@test raw_opts isa NamedTuple + Test.@test haskey(raw_opts, :reltol) + Test.@test haskey(raw_opts, :abstol) + Test.@test haskey(raw_opts, :maxiters) + + # Verify values + Test.@test raw_opts[:reltol] == 1e-8 + Test.@test raw_opts[:abstol] == 1e-10 + Test.@test raw_opts[:maxiters] == 1000 + end + + # ==================================================================== + # UNIT TESTS - Validation Error Throws + # ==================================================================== + + Test.@testset "Validation Error Throws" begin + Test.@testset "reltol must be positive" begin + redirect_stderr(devnull) do + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(reltol=-1.0) + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(reltol=0.0) + end + end + + Test.@testset "abstol must be positive" begin + redirect_stderr(devnull) do + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(abstol=-1.0) + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(abstol=0.0) + end + end + + Test.@testset "maxiters must be positive" begin + redirect_stderr(devnull) do + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(maxiters=-1) + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(maxiters=0) + end + end + + Test.@testset "dt must be positive" begin + redirect_stderr(devnull) do + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(dt=-0.1) + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(dt=0.0) + end + end + + Test.@testset "dtmax must be positive" begin + redirect_stderr(devnull) do + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(dtmax=-0.1) + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(dtmax=0.0) + end + end + + Test.@testset "dtmin must be positive" begin + redirect_stderr(devnull) do + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(dtmin=-1e-5) + Test.@test_throws Exceptions.IncorrectArgument Integrators.SciML(dtmin=0.0) + end + end + end + + # ==================================================================== + # UNIT TESTS - Problem Building + # ==================================================================== + + Test.@testset "Problem Building" begin + Test.@testset "builds ODEProblem without variable" begin + # Create a simple system + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + + config = Configs.StatePointConfig(0.0, [1.0, 2.0], 1.0) + integ = Integrators.SciML() + + # Build ODE problem + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + + Test.@test prob isa SciMLBase.AbstractODEProblem + Test.@test prob.p isa Common.ODEParameters + Test.@test Common.variable(prob.p) === nothing + end + + Test.@testset "builds ODEProblem with variable parameter" begin + # Create a simple system that takes a variable + sys = Systems.VectorFieldSystem( + Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + ) + + config = Configs.StatePointConfig(0.0, [1.0, 2.0], 1.0) + integ = Integrators.SciML() + + # Build ODE problem with variable + prob = Integrators.build_problem(integ, sys, config; variable=0.5, cache=nothing) + + Test.@test prob isa SciMLBase.AbstractODEProblem + Test.@test prob.p isa Common.ODEParameters + Test.@test Common.variable(prob.p) == 0.5 + end + end + + # ==================================================================== + # UNIT TESTS - Solving + # ==================================================================== + + Test.@testset "Solving" begin + # Create a simple system + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + + config = Configs.StatePointConfig(0.0, [1.0, 2.0], 1.0) + integ = Integrators.SciML(maxiters=1000, reltol=1e-6) + + # Build ODE problem + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + + # Build options + opts = Integrators.build_options(integ, config) + + # Solve + result = Integrators.solve_problem(integ, prob, opts) + + Test.@test result isa CTFlowsSciML.SciMLIntegrationResult + Test.@test result isa Integrators.AbstractIntegrationResult + end + + redirect_stderr(devnull) do + Test.@testset "SolverFailure on failed retcode" begin + # Create a simple ODE problem that will fail with maxiters=1 + prob = ODEProblem((du, u, p, t) -> du .= u, [1.0], (0.0, 1.0)) + integ = Integrators.SciML(maxiters=1) + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + opts = Integrators.build_options(integ, config) + + # Test that solve_problem throws SolverFailure when unsafe=false + Test.@test_throws Exceptions.SolverFailure Integrators.solve_problem(integ, prob, opts; unsafe=false) + + # Test the exception contains correct fields + try + Integrators.solve_problem(integ, prob, opts; unsafe=false) + catch e + Test.@test e isa Exceptions.SolverFailure + Test.@test occursin("MaxIters", e.retcode) + Test.@test e.context == "SciML solve_problem" + end + end + end + + redirect_stderr(devnull) do + Test.@testset "unsafe=true bypasses retcode check" begin + # Create a simple ODE problem that will fail with maxiters=1 + prob = ODEProblem((du, u, p, t) -> du .= u, [1.0], (0.0, 1.0)) + integ = Integrators.SciML(maxiters=1) + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + opts = Integrators.build_options(integ, config) + + # With unsafe=true, should not throw even with bad retcode + result = Integrators.solve_problem(integ, prob, opts; unsafe=true) + Test.@test result isa CTFlowsSciML.SciMLIntegrationResult + end + end + + # ==================================================================== + # UNIT TESTS - Semantic Accessors + # ==================================================================== + + Test.@testset "Semantic Accessors" begin + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 2.0]) + integ = Integrators.SciML(maxiters=1000, reltol=1e-6) + + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + + Test.@test Integrators.final_state(result) isa Vector{Float64} + Test.@test length(Integrators.final_state(result)) == 2 + + ts = Integrators.times(result) + Test.@test ts isa Vector{Float64} + Test.@test ts[1] == 0.0 + Test.@test ts[end] == 1.0 + + Test.@test Integrators.evaluate_at(result, 0.5) isa Vector{Float64} + end + + # ==================================================================== + # INTEGRATION TESTS - Full Workflow + # ==================================================================== + + Test.@testset "Full Workflow" begin + Test.@testset "StatePointConfig workflow" begin + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + + config = Configs.StatePointConfig(0.0, 1.0, 1.0) + integ = Integrators.SciML(maxiters=1000, reltol=1e-6) + + # Build problem + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + + # Build options + opts = Integrators.build_options(integ, config) + + # Solve + result = Integrators.solve_problem(integ, prob, opts) + + # Build solution + flow_sol = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + + Test.@test flow_sol isa Number + end + + Test.@testset "StateTrajectoryConfig workflow" begin + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 2.0]) + integ = Integrators.SciML(maxiters=1000, reltol=1e-6) + + # Build problem + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + + # Build options + opts = Integrators.build_options(integ, config) + + # Solve + result = Integrators.solve_problem(integ, prob, opts) + + # Build solution + flow_sol = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + + Test.@test flow_sol isa Solutions.VectorFieldSolution + end + end + + # ==================================================================== + # INTEGRATION TESTS - InPlace VF ร— mutable/immutable u0 + # ==================================================================== + + Test.@testset "InPlace VF โ€” SciML integration" begin + integ = Integrators.SciML(maxiters=10000, reltol=1e-8, abstol=1e-10) + # ODE: dx/dt = -x โ†’ x(t) = xโ‚€ ยท e^{-t} + + Test.@testset "OOP VF + mutable Vector u0" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = [1.0, 2.0] + config = Configs.StatePointConfig(0.0, u0, 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf โ‰ˆ exp(-1.0) .* [1.0, 2.0] atol=1e-5 + end + + Test.@testset "OOP VF + SVector u0" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = SA[1.0, 2.0] + config = Configs.StatePointConfig(0.0, u0, 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf โ‰ˆ exp(-1.0) .* [1.0, 2.0] atol=1e-5 + end + + Test.@testset "IP VF + mutable Vector u0" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = [1.0, 2.0] + config = Configs.StatePointConfig(0.0, u0, 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf โ‰ˆ exp(-1.0) .* [1.0, 2.0] atol=1e-5 + end + + Test.@testset "IP VF + SVector u0 (warns, uses rhs_oop_finalize)" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = SA[1.0, 2.0] + config = Configs.StatePointConfig(0.0, u0, 1.0) + prob = Test.@test_logs (:warn, r"InPlace VectorField") Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf โ‰ˆ exp(-1.0) .* [1.0, 2.0] atol=1e-5 + end + end + + # ==================================================================== + # INTEGRATION TESTS - InPlace HVF ร— mutable/immutable u0 + # ==================================================================== + + Test.@testset "InPlace HVF โ€” SciML integration" begin + integ = Integrators.SciML(maxiters=10000, reltol=1e-8, abstol=1e-10) + # ODE: dx/dt = x, dp/dt = -p โ†’ x(t)=xโ‚€ยทeแต—, p(t)=pโ‚€ยทe^{-t} + + Test.@testset "OOP HVF + mutable Vector u0" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + x0 = 1.0 + p0 = 0.5 + config = Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf[1] โ‰ˆ exp(1.0) atol=1e-5 + Test.@test xf[2] โ‰ˆ 0.5*exp(-1.0) atol=1e-5 + end + + Test.@testset "OOP HVF + SVector u0" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + x0 = SA[1.0] + p0 = SA[0.5] + config = Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf[1] โ‰ˆ exp(1.0) atol=1e-5 + Test.@test xf[2] โ‰ˆ 0.5*exp(-1.0) atol=1e-5 + end + + Test.@testset "IP HVF + mutable Vector u0" begin + hvf = Data.HamiltonianVectorField((dx, dp, x, p) -> (dx .= x; dp .= -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + x0 = 1.0 + p0 = 0.5 + config = Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf[1] โ‰ˆ exp(1.0) atol=1e-5 + Test.@test xf[2] โ‰ˆ 0.5*exp(-1.0) atol=1e-5 + end + + Test.@testset "IP HVF + SVector u0" begin + hvf = Data.HamiltonianVectorField((dx, dp, x, p) -> (dx .= x; dp .= -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + x0 = SA[1.0] + p0 = SA[0.5] + config = Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0) + prob = Test.@test_logs (:warn, r"InPlace HamiltonianVectorField") Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + xf = Integrators.final_state(result) + Test.@test xf[1] โ‰ˆ exp(1.0) atol=1e-5 + Test.@test xf[2] โ‰ˆ 0.5*exp(-1.0) atol=1e-5 + end + end + + # ==================================================================== + # UNIT TESTS - Config-Dependent Options + # ==================================================================== + + Test.@testset "Config-Dependent Options" begin + integ = Integrators.SciML() + config_point = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + config_traj = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + + Test.@testset "auto defaults resolve correctly for StatePointConfig" begin + opts = Integrators.build_options(integ, config_point) + Test.@test opts[:dense] === false + Test.@test opts[:save_everystep] === false + Test.@test opts[:save_start] === false + end + + Test.@testset "auto defaults resolve correctly for StateTrajectoryConfig" begin + opts = Integrators.build_options(integ, config_traj) + Test.@test opts[:dense] === true + Test.@test opts[:save_everystep] === true + Test.@test opts[:save_start] === true + end + + Test.@testset "explicit values override auto" begin + integ_explicit = Integrators.SciML(dense=false, save_everystep=true, save_start=false) + opts = Integrators.build_options(integ_explicit, config_traj) + Test.@test opts[:dense] === false + Test.@test opts[:save_everystep] === true + Test.@test opts[:save_start] === false + end + + Test.@testset "build_options dispatch returns correct cached dicts" begin + opts_point = Integrators.build_options(integ, config_point) + opts_traj = Integrators.build_options(integ, config_traj) + Test.@test opts_point === integ.options_point + Test.@test opts_traj === integ.options_trajectory + Test.@test opts_point !== opts_traj + end + end + + # ==================================================================== + # UNIT TESTS - Integrators.merge + # ==================================================================== + + Test.@testset "Integrators.merge" begin + Test.@testset "merge of single segment returns same result" begin + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + config = Configs.StatePointConfig(0.0, [1.0], 0.5) + integ = Integrators.SciML(reltol=1e-8) + prob = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + opts = Integrators.build_options(integ, config) + result = Integrators.solve_problem(integ, prob, opts) + merged = Integrators.merge([result]) + Test.@test merged === result + end + + Test.@testset "merge of two segments concatenates t and u" begin + sys = Systems.VectorFieldSystem( + Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + ) + integ = Integrators.SciML(reltol=1e-8) + + # First segment: [0, 0.5] + config1 = Configs.StatePointConfig(0.0, [1.0], 0.5) + prob1 = Integrators.build_problem(integ, sys, config1; variable=nothing, cache=nothing) + opts1 = Integrators.build_options(integ, config1) + result1 = Integrators.solve_problem(integ, prob1, opts1) + + # Second segment: [0.5, 1.0] + xf1 = Integrators.final_state(result1) + config2 = Configs.StatePointConfig(0.5, xf1, 1.0) + prob2 = Integrators.build_problem(integ, sys, config2; variable=nothing, cache=nothing) + opts2 = Integrators.build_options(integ, config2) + result2 = Integrators.solve_problem(integ, prob2, opts2) + + merged = Integrators.merge([result1, result2]) + Test.@test length(merged.ode_sol.t) > 1 + xf_merged = Integrators.final_state(merged) + Test.@test xf_merged[1] โ‰ˆ exp(-1.0) atol=1e-5 + end + + Test.@testset "merge of empty vector throws IncorrectArgument" begin + Test.@test_throws Exceptions.IncorrectArgument Integrators.merge(CTFlowsSciML.SciMLIntegrationResult[]) + end + end + end +end + +end # module + +test_sciml_extension() = TestSciMLExtension.test_sciml_extension() \ No newline at end of file diff --git a/test/suite/extensions/test_scimlbase_flow_constructors.jl b/test/suite/extensions/test_scimlbase_flow_constructors.jl new file mode 100644 index 00000000..a2a08c73 --- /dev/null +++ b/test/suite/extensions/test_scimlbase_flow_constructors.jl @@ -0,0 +1,172 @@ +module TestSciMLBaseFlowConstructors + +import Test +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Systems: Systems +import CTFlows.Integrators: Integrators +import CTFlows.Flows: Flows, AbstractFlow, StateFlow, build_flow +import CTFlows.Solutions: Solutions +import SciMLBase: SciMLBase, ODEProblem, ODEFunction +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector + +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_scimlbase_flow_constructors() + Test.@testset "SciMLBase Flow Constructors" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Flow(::AbstractODEFunction) + # ==================================================================== + + Test.@testset "Flow(::AbstractODEFunction)" begin + Test.@testset "in-place function returns StateFlow" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + Test.@test flow isa StateFlow + Test.@test flow isa AbstractFlow + Test.@test Flows.system(flow) isa CTFlowsSciML.SciMLFunctionSystem + end + + Test.@testset "out-of-place function returns StateFlow" begin + f = ODEFunction{false}((u, p, t) -> -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + Test.@test flow isa StateFlow + Test.@test flow isa AbstractFlow + Test.@test Flows.system(flow) isa CTFlowsSciML.SciMLFunctionSystem + end + + Test.@testset "kwargs passed to integrator" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + flow = Flows.Flow(f; reltol=1e-12, abstol=1e-12) + integ = Flows.integrator(flow) + Test.@test integ isa Integrators.SciML + end + end + + # ==================================================================== + # UNIT TESTS - Flow(::AbstractODEProblem) + # ==================================================================== + + Test.@testset "Flow(::AbstractODEProblem)" begin + Test.@testset "returns SciMLProblemFlow" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + flow = Flows.Flow(prob; reltol=1e-10) + Test.@test flow isa CTFlowsSciML.SciMLProblemFlow + Test.@test flow isa AbstractFlow + end + + Test.@testset "kwargs passed to integrator" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + prob = ODEProblem(f, [1.0], (0.0, 1.0)) + flow = Flows.Flow(prob; reltol=1e-12, abstol=1e-12) + integ = Flows.integrator(flow) + Test.@test integ isa Integrators.SciML + end + end + + # ==================================================================== + # INTEGRATION TESTS - Flow(::AbstractODEFunction) End-to-end + # ==================================================================== + + Test.@testset "Integration: Flow(::AbstractODEFunction)" begin + Test.@testset "in-place function end-to-end" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = flow(0.0, [1.0], 1.0; variable=2.0) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-2*1) * 1 = exp(-2) โ‰ˆ 0.1353 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + + Test.@testset "out-of-place function end-to-end" begin + f = ODEFunction{false}((u, p, t) -> -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = flow(0.0, [1.0], 1.0; variable=2.0) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Flow(::AbstractODEProblem) End-to-end + # ==================================================================== + + Test.@testset "Integration: Flow(::AbstractODEProblem)" begin + Test.@testset "point call end-to-end" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + flow = Flows.Flow(prob; reltol=1e-10) + xf = flow(0.0, [1.0], 1.0; variable=3.0) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-3*1) * 1 = exp(-3) โ‰ˆ 0.0498 + Test.@test isapprox(xf[1], exp(-3.0), rtol=1e-6) + end + + Test.@testset "trajectory call end-to-end" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + flow = Flows.Flow(prob; reltol=1e-10) + result = flow((0.0, 1.0), [1.0]; variable=3.0) + Test.@test result isa CTFlowsSciML.SciMLIntegrationResult + xf = Integrators.final_state(result) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-3*1) * 1 = exp(-3) โ‰ˆ 0.0498 + Test.@test isapprox(xf[1], exp(-3.0), rtol=1e-6) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Flow(::AbstractODEFunction) Trajectory + SVector + # ==================================================================== + + Test.@testset "Integration: Flow(::AbstractODEFunction) trajectory call" begin + Test.@testset "trajectory call returns VectorFieldSolution" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + sol = flow((0.0, 1.0), [1.0]; variable=2.0) + Test.@test sol isa Solutions.VectorFieldSolution + Test.@test sol(0.5)[1] โ‰ˆ exp(-2.0 * 0.5) rtol=1e-6 + end + end + + Test.@testset "Integration: Flow(::AbstractODEFunction) with variable" begin + Test.@testset "autonomous ODE" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = flow(0.0, [1.0, 2.0], 1.0; variable=1.0) + Test.@test xf isa Vector + Test.@test length(xf) == 2 + Test.@test xf โ‰ˆ [exp(-1.0), 2.0 * exp(-1.0)] rtol=1e-6 + end + end + + Test.@testset "Integration: iip ODEFunction + SVector u0 (cross-adapter path)" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = Test.@test_logs (:warn, r"InPlace SciMLFunction") flow(0.0, SA[1.0], 1.0; variable=2.0) + Test.@test xf isa SVector + Test.@test xf[1] โ‰ˆ exp(-2.0) rtol=1e-6 + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_scimlbase_flow_constructors() = TestSciMLBaseFlowConstructors.test_scimlbase_flow_constructors() diff --git a/test/suite/extensions/test_scimlbase_function_system.jl b/test/suite/extensions/test_scimlbase_function_system.jl new file mode 100644 index 00000000..fd756382 --- /dev/null +++ b/test/suite/extensions/test_scimlbase_function_system.jl @@ -0,0 +1,217 @@ +module TestSciMLBaseFunctionSystem + +import Test +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Configs: Configs +import CTFlows.Systems: Systems +import CTFlows.Integrators: Integrators +import CTFlows.Flows: Flows +import CTFlows.Solutions: Solutions +import CTSolvers.Strategies: Strategies + +# Fake tag type for testing stub behavior +struct FakeTag <: Common.AbstractTag end + +# Get extension to access SciML integrator +using SciMLBase: SciMLBase, ODEProblem, ODEFunction +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector + +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_scimlbase_function_system() + Test.@testset "SciMLBaseFunctionSystem" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Extension Loading + # ==================================================================== + + Test.@testset "Extension Loading" begin + Test.@testset "extension is loaded" begin + Test.@test !isnothing(CTFlowsSciML) + end + + Test.@testset "extension is a Module" begin + Test.@test CTFlowsSciML isa Module + end + end + + # ==================================================================== + # UNIT TESTS - SciMLFunctionSystem Construction + # ==================================================================== + + Test.@testset "SciMLFunctionSystem Construction" begin + Test.@testset "in-place ODEFunction" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + Test.@test sys isa CTFlowsSciML.SciMLFunctionSystem + Test.@test sys isa Systems.AbstractStateSystem + Test.@test sys.f === f + end + + Test.@testset "out-of-place ODEFunction" begin + f = ODEFunction{false}((u, p, t) -> -p .* u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + Test.@test sys isa CTFlowsSciML.SciMLFunctionSystem + Test.@test sys isa Systems.AbstractStateSystem + Test.@test sys.f === f + end + end + + # ==================================================================== + # UNIT TESTS - rhs / rhs_oop + # ==================================================================== + + Test.@testset "rhs Dispatch" begin + Test.@testset "in-place returns pre-computed closure" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + rhs_fn = Systems.rhs(sys) + Test.@test rhs_fn !== f # Not the raw function, but a wrapper + # Test that the wrapper works + du = zeros(2) + u = [1.0, 2.0] + p = Common.ODEParameters(2.0) + rhs_fn(du, u, p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + end + + Test.@testset "out-of-place returns pre-computed closure" begin + f = ODEFunction{false}((u, p, t) -> -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + rhs_oop_fn = Systems.rhs_oop(sys) + Test.@test rhs_oop_fn !== f # Not the raw function, but a wrapper + # Test that the wrapper works + u = [1.0, 2.0] + p = Common.ODEParameters(2.0) + du = rhs_oop_fn(u, p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + end + + Test.@testset "rhs on out-of-place returns iip wrapper (cross-adapter)" begin + f = ODEFunction{false}((u, p, t) -> -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + rhs_fn = Systems.rhs(sys) + # Should return a wrapper that makes the oop function iip + du = zeros(2) + u = [1.0, 2.0] + p = Common.ODEParameters(2.0) + rhs_fn(du, u, p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + end + + Test.@testset "rhs_oop on in-place returns oop wrapper (cross-adapter)" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + rhs_oop_fn = Systems.rhs_oop(sys) + # Should return a wrapper that allocates a buffer + u = [1.0, 2.0] + p = Common.ODEParameters(2.0) + du = rhs_oop_fn(u, p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + end + end + + # ==================================================================== + # UNIT TESTS - build_problem + # ==================================================================== + + Test.@testset "build_problem" begin + Test.@testset "returns raw ODEProblem (no wrapper)" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + integ = Integrators.SciML() + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=2.0, cache=nothing) + Test.@test prob isa SciMLBase.ODEProblem + Test.@test prob.p isa Common.ODEParameters + Test.@test prob.p.variable == 2.0 + end + + Test.@testset "variable wrapped in ODEParameters" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + integ = Integrators.SciML() + config = Configs.StatePointConfig(0.0, [1.0], 1.0) + prob = Integrators.build_problem(integ, sys, config; variable=3.5, cache=nothing) + Test.@test prob.p isa Common.ODEParameters + Test.@test prob.p.variable == 3.5 + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + Test.@testset "in-place display" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + str = sprint(show, sys) + Test.@test occursin("in-place", str) + end + + Test.@testset "out-of-place display" begin + f = ODEFunction{false}((u, p, t) -> -u) + sys = CTFlowsSciML.SciMLFunctionSystem(f) + str = sprint(show, sys) + Test.@test occursin("out-of-place", str) + end + end + + # ==================================================================== + # INTEGRATION TESTS - End-to-end + # ==================================================================== + + Test.@testset "Integration: iip flow end-to-end" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = flow(0.0, [1.0], 1.0; variable=2.0) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-2*1) * 1 = exp(-2) โ‰ˆ 0.1353 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + + Test.@testset "Integration: oop flow end-to-end" begin + f = ODEFunction{false}((u, p, t) -> -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = flow(0.0, [1.0], 1.0; variable=2.0) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + + Test.@testset "Integration: trajectory call" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + sol = flow((0.0, 1.0), [1.0]; variable=2.0) + Test.@test sol isa Solutions.VectorFieldSolution + Test.@test sol(0.5)[1] โ‰ˆ exp(-2.0 * 0.5) rtol=1e-6 + end + + Test.@testset "Integration: iip + SVector u0 (cross-adapter finalize path)" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f; reltol=1e-10) + xf = Test.@test_logs (:warn, r"InPlace SciMLFunction") flow(0.0, SA[1.0], 1.0; variable=2.0) + Test.@test xf isa SVector + Test.@test xf[1] โ‰ˆ exp(-2.0) rtol=1e-6 + end + + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_scimlbase_function_system() = TestSciMLBaseFunctionSystem.test_scimlbase_function_system() diff --git a/test/suite/extensions/test_scimlbase_problem_flow.jl b/test/suite/extensions/test_scimlbase_problem_flow.jl new file mode 100644 index 00000000..58703180 --- /dev/null +++ b/test/suite/extensions/test_scimlbase_problem_flow.jl @@ -0,0 +1,237 @@ +module TestSciMLBaseProblemFlow + +import Test +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Integrators: Integrators +import CTFlows.Flows: Flows, AbstractFlow + +# Get extension to access SciML integrator +using SciMLBase: SciMLBase, ODEProblem, ODEFunction +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_scimlbase_problem_flow() + Test.@testset "SciMLBaseProblemFlow" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "SciMLProblemFlow Construction" begin + Test.@testset "in-place ODEProblem" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + Test.@test flow isa CTFlowsSciML.SciMLProblemFlow + Test.@test flow isa AbstractFlow + Test.@test flow.prob === prob + Test.@test flow.integrator === integ + end + + Test.@testset "out-of-place ODEProblem" begin + f = ODEFunction{false}((u, p, t) -> -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + Test.@test flow isa CTFlowsSciML.SciMLProblemFlow + Test.@test flow isa AbstractFlow + Test.@test flow.prob === prob + Test.@test flow.integrator === integ + end + end + + # ==================================================================== + # UNIT TESTS - Contract Methods + # ==================================================================== + + Test.@testset "Contract Methods" begin + Test.@testset "system(f) returns nothing" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + prob = ODEProblem(f, [1.0], (0.0, 1.0)) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + Test.@test Flows.system(flow) === nothing + end + + Test.@testset "integrator(f) returns integrator" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + prob = ODEProblem(f, [1.0], (0.0, 1.0)) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + Test.@test Flows.integrator(flow) === integ + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + Test.@testset "compact show" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + prob = ODEProblem(f, [1.0], (0.0, 1.0)) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + str = sprint(show, flow) + Test.@test occursin("SciMLProblemFlow", str) + Test.@test occursin("tspan", str) + end + + Test.@testset "text/plain show" begin + f = ODEFunction((du, u, p, t) -> du .= -u) + prob = ODEProblem(f, [1.0], (0.0, 1.0)) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + str = sprint(show, MIME"text/plain"(), flow) + Test.@test occursin("SciMLProblemFlow", str) + Test.@test occursin("tspan:", str) + Test.@test occursin("u0:", str) + end + end + + # ==================================================================== + # INTEGRATION TESTS - No-arg Call + # ==================================================================== + + Test.@testset "Integration: No-arg Call" begin + Test.@testset "no-arg call uses original problem" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + result = flow(; unsafe=false) + Test.@test result isa CTFlowsSciML.SciMLIntegrationResult + xf = Integrators.final_state(result) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-2*1) * 1 = exp(-2) โ‰ˆ 0.1353 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Point Call + # ==================================================================== + + Test.@testset "Integration: Point Call" begin + Test.@testset "scalar state returns xf directly" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + xf = flow(0.0, [1.0], 1.0; unsafe=false) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-2*1) * 1 = exp(-2) โ‰ˆ 0.1353 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + + Test.@testset "vector state returns xf directly" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0, 2.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + xf = flow(0.0, [1.0, 2.0], 1.0; unsafe=false) + Test.@test xf isa Vector + Test.@test length(xf) == 2 + end + + Test.@testset "point call with variable overrides p" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + xf = flow(0.0, [1.0], 1.0; variable=3.0, unsafe=false) + # Expected: exp(-3*1) * 1 = exp(-3) โ‰ˆ 0.0498 + Test.@test isapprox(xf[1], exp(-3.0), rtol=1e-6) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Trajectory Call + # ==================================================================== + + Test.@testset "Integration: Trajectory Call" begin + Test.@testset "trajectory call returns SciMLIntegrationResult" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + result = flow((0.0, 1.0), [1.0]; unsafe=false) + Test.@test result isa CTFlowsSciML.SciMLIntegrationResult + xf = Integrators.final_state(result) + Test.@test xf isa Vector + Test.@test length(xf) == 1 + # Expected: exp(-2*1) * 1 = exp(-2) โ‰ˆ 0.1353 + Test.@test isapprox(xf[1], exp(-2.0), rtol=1e-6) + end + + Test.@testset "trajectory call with variable overrides p" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + result = flow((0.0, 1.0), [1.0]; variable=3.0, unsafe=false) + xf = Integrators.final_state(result) + # Expected: exp(-3*1) * 1 = exp(-3) โ‰ˆ 0.0498 + Test.@test isapprox(xf[1], exp(-3.0), rtol=1e-6) + end + + Test.@testset "trajectory call can evaluate at intermediate times" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + prob = ODEProblem(f, [1.0], (0.0, 1.0), 2.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + result = flow((0.0, 1.0), [1.0]; unsafe=false) + # Can evaluate at intermediate time + u_mid = Integrators.evaluate_at(result, 0.5) + Test.@test u_mid isa Vector + Test.@test isapprox(u_mid[1], exp(-2.0 * 0.5), rtol=1e-6) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Error Handling + # ==================================================================== + + Test.@testset "Error Handling" begin + Test.@testset "unsafe=true skips retcode check for point call" begin + # Create a problem that will fail (negative parameter with unstable ODE) + f = ODEFunction((du, u, p, t) -> du .= p .* u) + prob = ODEProblem(f, [1.0], (0.0, 10.0), 1.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + # This should not throw even if integration fails + xf = flow(0.0, [1.0], 10.0; unsafe=true) + Test.@test xf isa Vector + end + + Test.@testset "unsafe=true skips retcode check for trajectory call" begin + # Create a problem that will fail (negative parameter with unstable ODE) + f = ODEFunction((du, u, p, t) -> du .= p .* u) + prob = ODEProblem(f, [1.0], (0.0, 10.0), 1.0) + integ = Integrators.SciML() + flow = CTFlowsSciML.SciMLProblemFlow(prob, integ) + # This should not throw even if integration fails + result = flow((0.0, 10.0), [1.0]; unsafe=true) + Test.@test result isa CTFlowsSciML.SciMLIntegrationResult + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_scimlbase_problem_flow() = TestSciMLBaseProblemFlow.test_scimlbase_problem_flow() diff --git a/test/suite/flows/test_abstract_flow.jl b/test/suite/flows/test_abstract_flow.jl new file mode 100644 index 00000000..12508a60 --- /dev/null +++ b/test/suite/flows/test_abstract_flow.jl @@ -0,0 +1,303 @@ +module TestAbstractFlow + +import Test +import CTBase.Exceptions +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Traits +import CTFlows.Integrators +import CTFlows.Data +import CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake system for testing the AbstractFlow contract. + +This minimal implementation provides the required contract methods for AbstractSystem +to test flow behavior without full system complexity. +""" +struct FakeSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + state_dim::Int +end + +function Systems.rhs(sys::FakeSystem) + return (du, u, p, t) -> nothing +end + +struct FakeIntegrator <: Integrators.AbstractIntegrator + result::Any +end + +CTSolvers.Strategies.id(::Type{FakeIntegrator}) = :fake_integrator +CTSolvers.Strategies.metadata(::Type{FakeIntegrator}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.options(integ::FakeIntegrator) = CTSolvers.Strategies.StrategyOptions() + +""" +Fake flow for testing the AbstractFlow contract. + +This minimal implementation provides the required contract methods to test +routing and default behavior without full flow complexity. +""" +struct FakeFlow{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence} <: Flows.AbstractFlow{TD, VD} + sys::Systems.AbstractSystem{TD, VD} + integ::Any + function FakeFlow(sys::Systems.AbstractSystem, integ::Any) + return new{Traits.time_dependence(sys), Traits.variable_dependence(sys)}(sys, integ) + end +end + +function Flows.system(f::FakeFlow) + return f.sys +end + +function Flows.integrator(f::FakeFlow) + return f.integ +end + +function (f::FakeFlow)(t0, x0, tf) + return :fake_trajectory +end + +function (f::FakeFlow)(t0, x0, p0, tf) + return :fake_trajectory_with_costate +end + +function (f::FakeFlow)(config::Configs.AbstractConfig) + return :fake_config_trajectory +end + +""" +Minimal flow that does not implement the contract (for error testing). +""" +struct MinimalFlow <: Flows.AbstractFlow{Traits.Autonomous, Traits.Fixed} + sys::Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_flow() + Test.@testset "Abstract Flow Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + sys = FakeSystem(2) + Test.@test FakeFlow(sys, :fake_integ) isa Flows.AbstractFlow + Test.@test MinimalFlow(sys) isa Flows.AbstractFlow + end + + Test.@testset "Hierarchy" begin + sys = FakeSystem(2) + Test.@test isdefined(Flows, :AbstractStateFlow) + Test.@test isdefined(Flows, :AbstractHamiltonianFlow) + Test.@test isdefined(Flows, :StateFlow) + Test.@test isdefined(Flows, :HamiltonianFlow) + end + + # ==================================================================== + # UNIT TESTS - Contract Implementation + # ==================================================================== + + Test.@testset "Contract Implementation" begin + sys = FakeSystem(2) + flow = FakeFlow(sys, :fake_integ) + + Test.@testset "system returns system" begin + Test.@test Flows.system(flow) === sys + end + + Test.@testset "integrator returns integrator" begin + Test.@test Flows.integrator(flow) === :fake_integ + end + + Test.@testset "FakeFlow has correct VD parameter" begin + Test.@test flow isa FakeFlow{Traits.Autonomous, Traits.Fixed} + end + + Test.@testset "callable (t0, x0, tf)" begin + result = flow(0.0, [1.0, 0.0], 1.0) + Test.@test result === :fake_trajectory + end + + Test.@testset "callable (t0, x0, p0, tf)" begin + result = flow(0.0, [1.0, 0.0], [0.0, 0.0], 1.0) + Test.@test result === :fake_trajectory_with_costate + end + + Test.@testset "callable with config" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + result = flow(config) + Test.@test result === :fake_config_trajectory + end + + Test.@testset "callable with StateTrajectoryConfig" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + result = flow(config) + Test.@test result === :fake_config_trajectory + end + end + + # ==================================================================== + # UNIT TESTS - NotImplemented Errors + # ==================================================================== + + Test.@testset "NotImplemented Errors" begin + sys = FakeSystem(2) + flow = MinimalFlow(sys) + + Test.@testset "system throws NotImplemented" begin + try + Flows.system(flow) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("system", sprint(showerror, err)) + end + end + + Test.@testset "integrator throws NotImplemented" begin + try + Flows.integrator(flow) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("integrator", sprint(showerror, err)) + end + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + sys = FakeSystem(2) + flow = FakeFlow(sys, FakeIntegrator(0)) + + Test.@testset "MIME text/plain" begin + io = IOBuffer() + show(io, MIME("text/plain"), flow) + output = String(take!(io)) + Test.@test occursin("FakeFlow", output) + end + + Test.@testset "compact" begin + io = IOBuffer() + show(io, flow) + output = String(take!(io)) + Test.@test occursin("FakeFlow", output) + end + end + + # ==================================================================== + # UNIT TESTS - Predicate Methods + # ==================================================================== + + Test.@testset "Predicate Methods" begin + Test.@testset "FakeFlow with FakeSystem" begin + sys = FakeSystem(2) + int = FakeIntegrator(0) + flow = FakeFlow(sys, int) + + Test.@testset "is_autonomous" begin + Test.@test Traits.is_autonomous(flow) === true + end + + Test.@testset "is_nonautonomous" begin + Test.@test Traits.is_nonautonomous(flow) === false + end + + Test.@testset "is_variable" begin + Test.@test Traits.is_variable(flow) === false + end + + Test.@testset "is_nonvariable" begin + Test.@test Traits.is_nonvariable(flow) === true + end + + Test.@testset "has_variable" begin + Test.@test Traits.has_variable(flow) === false + end + end + + Test.@testset "MinimalFlow without system() - predicates work from type parameters" begin + sys = FakeSystem(2) + flow = MinimalFlow(sys) + + Test.@testset "is_autonomous works from type parameter" begin + # Predicates now read directly from type parameters, not from system + Test.@test Traits.is_autonomous(flow) === true + end + + Test.@testset "is_variable works from type parameter" begin + # Predicates now read directly from type parameters, not from system + Test.@test Traits.is_variable(flow) === false + end + end + end + + # ==================================================================== + # INTEGRATION TESTS - VectorField Flow + # ==================================================================== + + Test.@testset "VectorField Flow Integration Tests" begin + Test.@testset "Autonomous Fixed Flow" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + integ = :fake_integ + flow = FakeFlow(sys, integ) + + Test.@test flow isa FakeFlow{Traits.Autonomous, Traits.Fixed} + Test.@test Traits.is_autonomous(flow) === true + Test.@test Traits.is_nonautonomous(flow) === false + Test.@test Traits.is_variable(flow) === false + Test.@test Traits.is_nonvariable(flow) === true + Test.@test Traits.has_variable(flow) === false + end + + Test.@testset "NonAutonomous Fixed Flow" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + integ = :fake_integ + flow = FakeFlow(sys, integ) + + Test.@test flow isa FakeFlow{Traits.NonAutonomous, Traits.Fixed} + Test.@test Traits.is_autonomous(flow) === false + Test.@test Traits.is_nonautonomous(flow) === true + Test.@test Traits.is_variable(flow) === false + Test.@test Traits.is_nonvariable(flow) === true + Test.@test Traits.has_variable(flow) === false + end + + Test.@testset "Autonomous NonFixed Flow" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + sys = Systems.VectorFieldSystem(vf) + integ = :fake_integ + flow = FakeFlow(sys, integ) + + Test.@test flow isa FakeFlow{Traits.Autonomous, Traits.NonFixed} + Test.@test Traits.is_autonomous(flow) === true + Test.@test Traits.is_nonautonomous(flow) === false + Test.@test Traits.is_variable(flow) === true + Test.@test Traits.is_nonvariable(flow) === false + Test.@test Traits.has_variable(flow) === true + end + end + end +end + +end # module + +test_abstract_flow() = TestAbstractFlow.test_abstract_flow() diff --git a/test/suite/flows/test_building_flows.jl b/test/suite/flows/test_building_flows.jl new file mode 100644 index 00000000..98d2da0d --- /dev/null +++ b/test/suite/flows/test_building_flows.jl @@ -0,0 +1,164 @@ +module TestBuildingFlows + +import Test +using OrdinaryDiffEqTsit5 +import CTFlows.Data +import CTFlows.Flows +import CTFlows.Systems +import CTFlows.Integrators +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_building_flows() + Test.@testset "Building Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Flow constructor from VectorField + # ==================================================================== + + Test.@testset "Flow constructor from VectorField" begin + Test.@testset "default constructor" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + flow = Flows.Flow(vf) + + Test.@test flow isa Flows.StateFlow + Test.@test flow isa Flows.AbstractFlow + Test.@test flow.system isa Systems.VectorFieldSystem + Test.@test flow.integrator isa Integrators.AbstractIntegrator + end + + Test.@testset "with keyword options" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + flow = Flows.Flow(vf; reltol=1e-10) + + Test.@test flow isa Flows.StateFlow + Test.@test flow.integrator isa Integrators.AbstractIntegrator + end + end + + # ==================================================================== + # UNIT TESTS - Trait preservation + # ==================================================================== + + Test.@testset "Trait preservation" begin + Test.@testset "Autonomous Fixed" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + flow = Flows.Flow(vf) + + Test.@test Traits.time_dependence(flow) === Traits.Autonomous + Test.@test Traits.variable_dependence(flow) === Traits.Fixed + end + + Test.@testset "NonAutonomous Fixed" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + flow = Flows.Flow(vf) + + Test.@test Traits.time_dependence(flow) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(flow) === Traits.Fixed + end + + Test.@testset "Autonomous NonFixed" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + flow = Flows.Flow(vf) + + Test.@test Traits.time_dependence(flow) === Traits.Autonomous + Test.@test Traits.variable_dependence(flow) === Traits.NonFixed + end + + Test.@testset "NonAutonomous NonFixed" begin + vf = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + flow = Flows.Flow(vf) + + Test.@test Traits.time_dependence(flow) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(flow) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - System and Integrator access + # ==================================================================== + + Test.@testset "System and Integrator access" begin + vf = Data.VectorField(x -> 2 .* x; is_autonomous=true, is_variable=false) + flow = Flows.Flow(vf) + + Test.@testset "system accessor returns correct system" begin + sys = Flows.system(flow) + Test.@test sys isa Systems.VectorFieldSystem + end + + Test.@testset "integrator accessor returns correct integrator" begin + integ = Flows.integrator(flow) + Test.@test integ isa Integrators.AbstractIntegrator + end + end + + # ==================================================================== + # UNIT TESTS - Integration with build_system + # ==================================================================== + + Test.@testset "Integration with build_system" begin + Test.@testset "build_system is called internally" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + + # Build system directly + sys_direct = Systems.build_system(vf) + + # Build flow which should use build_system internally + flow = Flows.Flow(vf) + sys_from_flow = Flows.system(flow) + + # Both should be VectorFieldSystem with same traits + Test.@test sys_direct isa Systems.VectorFieldSystem + Test.@test sys_from_flow isa Systems.VectorFieldSystem + Test.@test Traits.time_dependence(sys_direct) === Traits.time_dependence(sys_from_flow) + Test.@test Traits.variable_dependence(sys_direct) === Traits.variable_dependence(sys_from_flow) + end + end + + # ==================================================================== + # UNIT TESTS - HamiltonianFlow constructor from HamiltonianVectorField + # ==================================================================== + + Test.@testset "HamiltonianFlow constructor from HamiltonianVectorField" begin + Test.@testset "default constructor" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + flow = Flows.Flow(hvf) + + Test.@test flow isa Flows.HamiltonianFlow + Test.@test flow isa Flows.AbstractFlow + Test.@test flow.system isa Systems.HamiltonianVectorFieldSystem + Test.@test flow.integrator isa Integrators.AbstractIntegrator + end + + Test.@testset "with keyword options" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + flow = Flows.Flow(hvf; reltol=1e-10) + + Test.@test flow isa Flows.HamiltonianFlow + Test.@test flow.integrator isa Integrators.AbstractIntegrator + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Flow constructor is exported" begin + Test.@test isdefined(Flows, :Flow) + end + end + end +end + +end # module + +test_building_flows() = TestBuildingFlows.test_building_flows() \ No newline at end of file diff --git a/test/suite/flows/test_calling_flows.jl b/test/suite/flows/test_calling_flows.jl new file mode 100644 index 00000000..d0fadb3c --- /dev/null +++ b/test/suite/flows/test_calling_flows.jl @@ -0,0 +1,373 @@ +module TestCallingFlows + +import Test +import CTFlows.Systems +import CTFlows.Data +import CTFlows.Differentiation +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Solutions +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Traits +import ADTypes +import DifferentiationInterface +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing the calling workflow +# ============================================================================== + +""" +Fake system for testing the calling workflow. +""" +struct FakeSystemForCalling <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + state_dim::Int +end + +struct FakeSystemNonFixed <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.NonFixed} + state_dim::Int +end + +""" +Fake Hamiltonian system for testing the calling workflow. +""" +struct FakeHamiltonianSystemForCalling <: Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithoutAD} + state_dim::Int +end + +""" +Fake Hamiltonian system with AD trait for testing cache preparation. +""" +struct FakeHamiltonianSystemWithAD <: Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithAD} + state_dim::Int +end + +function Systems.hamiltonian(sys::FakeHamiltonianSystemWithAD) + return Data.Hamiltonian((x, p) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) +end + +function Systems.backend(sys::FakeHamiltonianSystemWithAD) + return Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=true) +end + +# function Differentiation.prepare_cache( +# backend::Differentiation.DifferentiationInterface, +# h::Data.AbstractHamiltonian, +# typical_t, typical_x, typical_p, typical_v +# ) +# return nothing +# end + +""" +Fake integration result. +""" +struct FakeIntegrationResultForCalling <: Integrators.AbstractIntegrationResult end + +Integrators.final_state(::FakeIntegrationResultForCalling) = :fake_flow_solution + +""" +Fake integrator for testing the calling workflow. +Tracks which methods were called. +""" +mutable struct FakeIntegratorForCalling <: Integrators.AbstractIntegrator + build_problem_called::Bool + build_options_called::Bool + solve_problem_called::Bool + problem_result::Any + ode_solution::Any +end + +function FakeIntegratorForCalling() + return FakeIntegratorForCalling(false, false, false, nothing, nothing) +end + +# Implement named functions instead of callables +function Integrators.build_problem(integ::FakeIntegratorForCalling, system::Systems.AbstractSystem, config::Configs.AbstractConfig; variable=nothing, cache=nothing) + integ.build_problem_called = true + p = Common.ODEParameters(variable, cache) + integ.problem_result = :fake_ode_problem + return integ.problem_result +end + +function Integrators.build_options(integ::FakeIntegratorForCalling, config::Union{Configs.AbstractConfig, Nothing}) + integ.build_options_called = true + return Dict{Symbol,Any}() +end + +function Integrators.solve_problem(integ::FakeIntegratorForCalling, prob, options::Dict{Symbol,Any}; unsafe=false) + integ.solve_problem_called = true + integ.ode_solution = FakeIntegrationResultForCalling() + return integ.ode_solution +end + +function Solutions.build_solution( + ::Type{Traits.PointTrait}, + ::Type{Traits.StateTrait}, + config::Configs.AbstractConfig, + result::FakeIntegrationResultForCalling, +) + return Integrators.final_state(result) +end + +function Solutions.build_solution( + ::Type{Traits.TrajectoryTrait}, + ::Type{Traits.StateTrait}, + config::Configs.AbstractConfig, + result::FakeIntegrationResultForCalling, +) + return :fake_vector_field_solution +end + +function Solutions.build_solution( + ::Type{Traits.PointTrait}, + ::Type{Traits.HamiltonianTrait}, + config::Configs.AbstractConfig, + result::FakeIntegrationResultForCalling, +) + return (:fake_xf, :fake_pf) +end + +function Solutions.build_solution( + ::Type{Traits.TrajectoryTrait}, + ::Type{Traits.HamiltonianTrait}, + config::Configs.AbstractConfig, + result::FakeIntegrationResultForCalling, +) + return :fake_hamiltonian_solution +end + +""" +Fake flow for testing the calling workflow. +""" +struct FakeFlowForCalling{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, S<:Systems.AbstractSystem{TD, VD}, I} <: Flows.AbstractFlow{TD, VD} + sys::S + integ::I +end + +function Flows.system(flow::FakeFlowForCalling) + return flow.sys +end + +function Flows.integrator(flow::FakeFlowForCalling) + return flow.integ +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_calling_flows() + Test.@testset "Calling Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - call function workflow + # ==================================================================== + + Test.@testset "call function workflow" begin + Test.@testset "all steps are executed in order" begin + # Setup + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + # Execute + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + # Verify all steps were called + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + + # Verify result - for StatePointConfig it unwraps the vector + Test.@test result == :fake_flow_solution + end + + Test.@testset "call with variable parameter (Fixed system) โ†’ PreconditionError" begin + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + # Call with variable (should now raise PreconditionError for Fixed flow) + Test.@test_throws Exceptions.PreconditionError Flows.call(flow, config; variable=0.5, unsafe=false) + end + + Test.@testset "call with StateTrajectoryConfig" begin + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 0.0]) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result === :fake_vector_field_solution + end + + Test.@testset "call with unsafe kwarg" begin + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + # Call with unsafe=true + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=true) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == :fake_flow_solution + end + + Test.@testset "call with HamiltonianPointConfig" begin + sys = FakeHamiltonianSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == (:fake_xf, :fake_pf) + end + + Test.@testset "call with HamiltonianTrajectoryConfig" begin + sys = FakeHamiltonianSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 0.0], [0.5, 0.3]) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result === :fake_hamiltonian_solution + end + end + + # ==================================================================== + # INTEGRATION TESTS - Cache in pipeline (trait dispatch) + # ==================================================================== + + Test.@testset "Integration: Cache in pipeline" begin + Test.@testset "HamiltonianVectorFieldSystem (WithoutAD) โ€” cache is nothing" begin + sys = FakeHamiltonianSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == (:fake_xf, :fake_pf) + end + + Test.@testset "HamiltonianSystem (WithAD, prepare_cache=true) โ€” cache prepared" begin + # This would require a real AD backend, so we skip the actual cache check + # The trait dispatch is tested via the fake system + sys = FakeHamiltonianSystemWithAD(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.HamiltonianPointConfig(0.0, [1.0, 0.0], [0.5, 0.3], 1.0) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == (:fake_xf, :fake_pf) + end + + Test.@testset "Regression: existing StateFlow path unchanged" begin + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == :fake_flow_solution + end + + end + + # ==================================================================== + # UNIT TESTS - Dispatch: 4-way trait dispatch + # ==================================================================== + + Test.@testset "Dispatch: Fixed + NotProvided โ†’ core_call (no error)" begin + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + result = Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == :fake_flow_solution + end + + Test.@testset "Dispatch: Fixed + variable provided โ†’ PreconditionError" begin + sys = FakeSystemForCalling(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.Fixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + Test.@test_throws Exceptions.PreconditionError Flows.call(flow, config; variable=0.5, unsafe=false) + end + + Test.@testset "Dispatch: NonFixed + variable provided โ†’ core_call" begin + sys = FakeSystemNonFixed(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.NonFixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + result = Flows.call(flow, config; variable=0.5, unsafe=false) + + Test.@test integ.build_problem_called === true + Test.@test integ.build_options_called === true + Test.@test integ.solve_problem_called === true + Test.@test result == :fake_flow_solution + end + + Test.@testset "Dispatch: NonFixed + NotProvided โ†’ PreconditionError" begin + sys = FakeSystemNonFixed(2) + integ = FakeIntegratorForCalling() + flow = FakeFlowForCalling{Traits.Autonomous, Traits.NonFixed, typeof(sys), typeof(integ)}(sys, integ) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + Test.@test_throws Exceptions.PreconditionError Flows.call(flow, config; variable=Common.NotProvided(), unsafe=false) + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "call function is exported" begin + Test.@test isdefined(Flows, :call) + end + end + end +end + +end # module + +test_calling_flows() = TestCallingFlows.test_calling_flows() \ No newline at end of file diff --git a/test/suite/flows/test_flow.jl b/test/suite/flows/test_flow.jl new file mode 100644 index 00000000..b72bda6a --- /dev/null +++ b/test/suite/flows/test_flow.jl @@ -0,0 +1,340 @@ +module TestFlow + +import Test +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Traits +import CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing +# ============================================================================== + +""" +Fake integrator for testing Flow. + +Implements minimal strategy contract for testing purposes. +""" +struct FakeIntegrator <: Integrators.AbstractIntegrator + result::Any +end + +# Minimal strategy contract implementation +CTSolvers.Strategies.id(::Type{FakeIntegrator}) = :fake_integrator +CTSolvers.Strategies.metadata(::Type{FakeIntegrator}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.options(integ::FakeIntegrator) = CTSolvers.Strategies.StrategyOptions() + +""" +Fake flow for testing Flow contract without requiring SciML extension. + +Matches the new parametric Flow{TD, VD, S, I} structure. +""" +struct FakeFlow{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, S<:Systems.AbstractSystem{TD, VD}, I} <: Flows.AbstractFlow{TD, VD} + sys::S + integ::I +end + +""" +Fake system for Fixed systems. +""" +struct FixedSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} end + +""" +Fake system for NonFixed systems. +""" +struct NonFixedSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.NonFixed} end + +function Flows.system(flow::FakeFlow) + return flow.sys +end + +function Flows.integrator(flow::FakeFlow) + return flow.integ +end + +# Config-based callable - both Fixed and NonFixed require variable, unsafe +function (flow::FakeFlow)(config::Configs.StatePointConfig; variable, unsafe) + return flow.integ.result +end + +function (flow::FakeFlow)(config::Configs.StateTrajectoryConfig; variable, unsafe) + return flow.integ.result +end + +# Positional callable - both Fixed and NonFixed require variable, unsafe +function (flow::FakeFlow)(t0, x0, tf; variable, unsafe) + return flow.integ.result +end + +function (flow::FakeFlow)(tspan::Tuple, x0; variable, unsafe) + return flow.integ.result +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow() + Test.@testset "Flow Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Flow Construction + # ==================================================================== + + Test.@testset "Flow Construction" begin + Test.@testset "Fixed Flow" begin + sys = FixedSystem() + integ = FakeIntegrator(:fake_ode_sol) + flow = FakeFlow{Traits.Autonomous, Traits.Fixed, FixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "Flow is AbstractFlow{Autonomous, Fixed}" begin + Test.@test flow isa Flows.AbstractFlow{Traits.Autonomous, Traits.Fixed} + end + + Test.@testset "Flow stores system" begin + Test.@test Flows.system(flow) === sys + end + + Test.@testset "Flow stores integrator" begin + Test.@test Flows.integrator(flow) === integ + end + end + + Test.@testset "NonFixed Flow" begin + sys = NonFixedSystem() + integ = FakeIntegrator(:fake_ode_sol) + flow = FakeFlow{Traits.Autonomous, Traits.NonFixed, NonFixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "Flow is AbstractFlow{Autonomous, NonFixed}" begin + Test.@test flow isa Flows.AbstractFlow{Traits.Autonomous, Traits.NonFixed} + end + + Test.@testset "Flow stores system" begin + Test.@test Flows.system(flow) === sys + end + + Test.@testset "Flow stores integrator" begin + Test.@test Flows.integrator(flow) === integ + end + end + end + + # ==================================================================== + # UNIT TESTS - build_flow + # ==================================================================== + + Test.@testset "build_flow" begin + Test.@testset "Fixed System" begin + sys = FixedSystem() + integ = FakeIntegrator(:solution) + flow = Flows.build_flow(sys, integ) + + Test.@testset "returns StateFlow" begin + Test.@test flow isa Flows.StateFlow + end + + Test.@testset "Flow has correct traits" begin + Test.@test flow isa Flows.AbstractFlow{Traits.Autonomous, Traits.Fixed} + end + + Test.@testset "Flow stores system" begin + Test.@test Flows.system(flow) === sys + end + + Test.@testset "Flow stores integrator" begin + Test.@test Flows.integrator(flow) === integ + end + end + + Test.@testset "NonFixed System" begin + sys = NonFixedSystem() + integ = FakeIntegrator(:solution) + flow = Flows.build_flow(sys, integ) + + Test.@testset "returns StateFlow" begin + Test.@test flow isa Flows.StateFlow + end + + Test.@testset "Flow has correct traits" begin + Test.@test flow isa Flows.AbstractFlow{Traits.Autonomous, Traits.NonFixed} + end + + Test.@testset "Flow stores system" begin + Test.@test Flows.system(flow) === sys + end + + Test.@testset "Flow stores integrator" begin + Test.@test Flows.integrator(flow) === integ + end + end + end + + # ==================================================================== + # UNIT TESTS - Flow Callable (Fixed systems) + # ==================================================================== + + Test.@testset "Flow Callable - Fixed Systems" begin + sys = FixedSystem() + integ = FakeIntegrator(:solution) + flow = FakeFlow{Traits.Autonomous, Traits.Fixed, FixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "call with StatePointConfig" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + result = flow(config; variable=nothing, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with StatePointConfig and variable (ignored for Fixed)" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + result = flow(config; variable=0.5, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (t0, x0, tf)" begin + result = flow(0.0, [1.0, 0.0], 1.0; variable=nothing, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (t0, x0, tf; variable) (ignored for Fixed)" begin + result = flow(0.0, [1.0, 0.0], 1.0; variable=0.5, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (tspan, x0)" begin + result = flow((0.0, 1.0), [1.0, 0.0]; variable=nothing, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (tspan, x0; variable) (ignored for Fixed)" begin + result = flow((0.0, 1.0), [1.0, 0.0]; variable=0.5, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with unsafe kwarg" begin + result = flow(0.0, [1.0, 0.0], 1.0; variable=nothing, unsafe=true) + Test.@test result === :solution + end + end + + # ==================================================================== + # UNIT TESTS - Flow Callable (NonFixed systems) + # ==================================================================== + + Test.@testset "Flow Callable - NonFixed Systems" begin + sys = NonFixedSystem() + integ = FakeIntegrator(:solution) + flow = FakeFlow{Traits.Autonomous, Traits.NonFixed, NonFixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "call with StatePointConfig" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + result = flow(config; variable=nothing, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with StatePointConfig and variable" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + result = flow(config; variable=0.5, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (t0, x0, tf)" begin + result = flow(0.0, [1.0, 0.0], 1.0; variable=nothing, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (t0, x0, tf; variable)" begin + result = flow(0.0, [1.0, 0.0], 1.0; variable=0.5, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (tspan, x0)" begin + result = flow((0.0, 1.0), [1.0, 0.0]; variable=nothing, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with (tspan, x0; variable)" begin + result = flow((0.0, 1.0), [1.0, 0.0]; variable=0.5, unsafe=false) + Test.@test result === :solution + end + + Test.@testset "call with unsafe kwarg" begin + result = flow(0.0, [1.0, 0.0], 1.0; variable=nothing, unsafe=true) + Test.@test result === :solution + end + end + + # ==================================================================== + # UNIT TESTS - Trait Delegation + # ==================================================================== + + Test.@testset "Trait Delegation" begin + Test.@testset "Fixed Flow traits" begin + sys = FixedSystem() + integ = FakeIntegrator(:solution) + flow = FakeFlow{Traits.Autonomous, Traits.Fixed, FixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "variable_dependence delegates to system" begin + Test.@test Traits.variable_dependence(flow) === Traits.Fixed + end + + Test.@testset "time_dependence delegates to system" begin + Test.@test Traits.time_dependence(flow) === Traits.Autonomous + end + end + + Test.@testset "NonFixed Flow traits" begin + sys = NonFixedSystem() + integ = FakeIntegrator(:solution) + flow = FakeFlow{Traits.Autonomous, Traits.NonFixed, NonFixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "variable_dependence delegates to system" begin + Test.@test Traits.variable_dependence(flow) === Traits.NonFixed + end + + Test.@testset "time_dependence delegates to system" begin + Test.@test Traits.time_dependence(flow) === Traits.Autonomous + end + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + sys = FixedSystem() + integ = FakeIntegrator(:fake_ode_sol) + flow = FakeFlow{Traits.Autonomous, Traits.Fixed, FixedSystem, typeof(integ)}(sys, integ) + + Test.@testset "MIME text/plain" begin + io = IOBuffer() + show(io, MIME("text/plain"), flow) + output = String(take!(io)) + Test.@test occursin("Flow", output) + Test.@test occursin("system", output) + Test.@test occursin("integrator", output) + Test.@test occursin(" system: ", output) + Test.@test occursin(" integrator: ", output) + end + + Test.@testset "compact" begin + io = IOBuffer() + show(io, flow) + output = String(take!(io)) + Test.@test occursin("Flow", output) + Test.@test occursin("system=", output) + Test.@test occursin("integrator=", output) + end + end + end +end + +end # module + +test_flow() = TestFlow.test_flow() diff --git a/test/suite/flows/test_flow_callables.jl b/test/suite/flows/test_flow_callables.jl new file mode 100644 index 00000000..2fcb4a24 --- /dev/null +++ b/test/suite/flows/test_flow_callables.jl @@ -0,0 +1,283 @@ +module TestFlowCallables + +import Test +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Solutions +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Traits +import CTBase.Exceptions + +using StaticArrays: SA + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing flow callables +# ============================================================================== + +struct FakeStateSystemFC <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + n::Int +end + +struct FakeHamSysFC <: Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithoutAD} + n::Int +end + +struct FakeResultFC <: Integrators.AbstractIntegrationResult end + +Integrators.final_state(::FakeResultFC) = [0.0, 0.0] + +mutable struct FakeIntegFC <: Integrators.AbstractIntegrator + last_config::Any +end + +function FakeIntegFC() + return FakeIntegFC(nothing) +end + +function Integrators.build_problem(integ::FakeIntegFC, sys::Systems.AbstractSystem, config::Configs.AbstractConfig; variable=nothing, cache=nothing) + integ.last_config = config + return :fake_prob +end + +function Integrators.build_options(integ::FakeIntegFC, config::Union{Configs.AbstractConfig, Nothing}) + return Dict{Symbol,Any}() +end + +function Integrators.solve_problem(integ::FakeIntegFC, prob, options::Dict{Symbol,Any}; unsafe=false) + return FakeResultFC() +end + +function Solutions.build_solution(::Type{Traits.PointTrait}, ::Type{Traits.StateTrait}, config::Configs.AbstractConfig, result::FakeResultFC) + return :state_point_sol +end + +function Solutions.build_solution(::Type{Traits.TrajectoryTrait}, ::Type{Traits.StateTrait}, config::Configs.AbstractConfig, result::FakeResultFC) + return :state_traj_sol +end + +function Solutions.build_solution(::Type{Traits.PointTrait}, ::Type{Traits.HamiltonianTrait}, config::Configs.AbstractConfig, result::FakeResultFC) + return :ham_point_sol +end + +function Solutions.build_solution(::Type{Traits.TrajectoryTrait}, ::Type{Traits.HamiltonianTrait}, config::Configs.AbstractConfig, result::FakeResultFC) + return :ham_traj_sol +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow_callables() + Test.@testset "Flow Callables Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - StateFlow (t0, x0, tf) -> StatePointConfig + # ==================================================================== + + Test.@testset "StateFlow (t0, x0, tf) -> StatePointConfig" begin + integ = FakeIntegFC(nothing) + sys = FakeStateSystemFC(2) + flow = Flows.StateFlow(sys, integ) + + Test.@testset "scalar x0" begin + x0 = 1.0 + result = flow(0.0, x0, 1.0) + Test.@test integ.last_config isa Configs.StatePointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_point_sol + end + + Test.@testset "vector x0" begin + x0 = [1.0, 0.0] + result = flow(0.0, x0, 1.0) + Test.@test integ.last_config isa Configs.StatePointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_point_sol + end + + Test.@testset "SVector x0" begin + x0 = SA[1.0, 0.0] + result = flow(0.0, x0, 1.0) + Test.@test integ.last_config isa Configs.StatePointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_point_sol + end + + Test.@testset "matrix x0" begin + x0 = [1.0 2.0; 3.0 4.0] + result = flow(0.0, x0, 1.0) + Test.@test integ.last_config isa Configs.StatePointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_point_sol + end + end + + # ==================================================================== + # UNIT TESTS - StateFlow (tspan, x0) -> StateTrajectoryConfig + # ==================================================================== + + Test.@testset "StateFlow (tspan, x0) -> StateTrajectoryConfig" begin + integ = FakeIntegFC(nothing) + sys = FakeStateSystemFC(2) + flow = Flows.StateFlow(sys, integ) + + Test.@testset "vector x0" begin + x0 = [1.0, 0.0] + result = flow((0.0, 1.0), x0) + Test.@test integ.last_config isa Configs.StateTrajectoryConfig + Test.@test integ.last_config.tspan == (0.0, 1.0) + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_traj_sol + end + + Test.@testset "SVector x0" begin + x0 = SA[1.0, 0.0] + result = flow((0.0, 1.0), x0) + Test.@test integ.last_config isa Configs.StateTrajectoryConfig + Test.@test integ.last_config.tspan == (0.0, 1.0) + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_traj_sol + end + + Test.@testset "matrix x0" begin + x0 = [1.0 2.0; 3.0 4.0] + result = flow((0.0, 1.0), x0) + Test.@test integ.last_config isa Configs.StateTrajectoryConfig + Test.@test integ.last_config.tspan == (0.0, 1.0) + Test.@test integ.last_config.x0 === x0 + Test.@test result === :state_traj_sol + end + end + + # ==================================================================== + # UNIT TESTS - HamiltonianFlow (t0, x0, p0, tf) -> HamiltonianPointConfig + # ==================================================================== + + Test.@testset "HamiltonianFlow (t0, x0, p0, tf) -> HamiltonianPointConfig" begin + integ = FakeIntegFC(nothing) + sys = FakeHamSysFC(2) + flow = Flows.HamiltonianFlow(sys, integ) + + Test.@testset "scalar x0, p0" begin + x0 = 1.0 + p0 = 0.0 + result = flow(0.0, x0, p0, 1.0) + Test.@test integ.last_config isa Configs.HamiltonianPointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test integ.last_config.p0 === p0 + Test.@test result === :ham_point_sol + end + + Test.@testset "vector x0, p0" begin + x0 = [1.0, 0.0] + p0 = [0.0, 1.0] + result = flow(0.0, x0, p0, 1.0) + Test.@test integ.last_config isa Configs.HamiltonianPointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test integ.last_config.p0 === p0 + Test.@test result === :ham_point_sol + end + + Test.@testset "SVector x0, p0" begin + x0 = SA[1.0, 0.0] + p0 = SA[0.0, 1.0] + result = flow(0.0, x0, p0, 1.0) + Test.@test integ.last_config isa Configs.HamiltonianPointConfig + Test.@test integ.last_config.t0 == 0.0 + Test.@test integ.last_config.tf == 1.0 + Test.@test integ.last_config.x0 === x0 + Test.@test integ.last_config.p0 === p0 + Test.@test result === :ham_point_sol + end + end + + # ==================================================================== + # UNIT TESTS - HamiltonianFlow (tspan, x0, p0) -> HamiltonianTrajectoryConfig + # ==================================================================== + + Test.@testset "HamiltonianFlow (tspan, x0, p0) -> HamiltonianTrajectoryConfig" begin + integ = FakeIntegFC(nothing) + sys = FakeHamSysFC(2) + flow = Flows.HamiltonianFlow(sys, integ) + + Test.@testset "vector x0, p0" begin + x0 = [1.0, 0.0] + p0 = [0.0, 1.0] + result = flow((0.0, 1.0), x0, p0) + Test.@test integ.last_config isa Configs.HamiltonianTrajectoryConfig + Test.@test integ.last_config.tspan == (0.0, 1.0) + Test.@test integ.last_config.x0 === x0 + Test.@test integ.last_config.p0 === p0 + Test.@test result === :ham_traj_sol + end + + Test.@testset "SVector x0, p0" begin + x0 = SA[1.0, 0.0] + p0 = SA[0.0, 1.0] + result = flow((0.0, 1.0), x0, p0) + Test.@test integ.last_config isa Configs.HamiltonianTrajectoryConfig + Test.@test integ.last_config.tspan == (0.0, 1.0) + Test.@test integ.last_config.x0 === x0 + Test.@test integ.last_config.p0 === p0 + Test.@test result === :ham_traj_sol + end + end + + # ==================================================================== + # UNIT TESTS - variable and unsafe kwarg propagation + # ==================================================================== + + Test.@testset "variable and unsafe kwarg propagation" begin + Test.@testset "StateFlow with variable kwarg โ†’ PreconditionError" begin + integ = FakeIntegFC(nothing) + sys = FakeStateSystemFC(2) + flow = Flows.StateFlow(sys, integ) + # Fixed flows must not receive a variable parameter + Test.@test_throws Exceptions.PreconditionError flow(0.0, [1.0, 0.0], 1.0; variable=0.5, unsafe=true) + end + + Test.@testset "HamiltonianFlow with variable kwarg โ†’ PreconditionError" begin + integ = FakeIntegFC(nothing) + sys = FakeHamSysFC(2) + flow = Flows.HamiltonianFlow(sys, integ) + # Fixed flows must not receive a variable parameter + Test.@test_throws Exceptions.PreconditionError flow(0.0, [1.0, 0.0], [0.0, 1.0], 1.0; variable=0.5, unsafe=true) + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "StateFlow is exported" begin + Test.@test isdefined(Flows, :StateFlow) + end + + Test.@testset "HamiltonianFlow is exported" begin + Test.@test isdefined(Flows, :HamiltonianFlow) + end + end + end +end + +end # module + +test_flow_callables() = TestFlowCallables.test_flow_callables() diff --git a/test/suite/flows/test_flow_module.jl b/test/suite/flows/test_flow_module.jl new file mode 100644 index 00000000..936e65de --- /dev/null +++ b/test/suite/flows/test_flow_module.jl @@ -0,0 +1,248 @@ +""" +# ============================================================================ +# Flows Module Exports Tests +# ============================================================================ +# This file tests the exports from the `Flows` module. It verifies that +# the expected types, functions, and constructors are properly exported by +# `CTFlows.Flows` and readily accessible to the end user. +""" + +module TestFlowModule + +import Test +import CTFlows.Flows +import CTFlows.Systems +import CTFlows.Integrators +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits +import CTSolvers: CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestFlowModule + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake system for testing the Flow contract. + +This minimal implementation provides the required contract methods to test +routing and default behavior without full system complexity. +""" +struct FakeSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + state_dim::Int + param_dim::Int +end + +# Implement contract: rhs +function Systems.rhs(sys::FakeSystem) + return (du, u, p, t) -> du .= -u +end + +# Implement contract: time_dependence +function Traits.time_dependence(sys::FakeSystem) + return Traits.Autonomous +end + +# Implement contract: variable_dependence +function Traits.variable_dependence(sys::FakeSystem) + return Traits.Fixed +end + +""" +Fake integrator for testing the Flow contract. +""" +struct FakeIntegrator <: Integrators.AbstractIntegrator + options::CTSolvers.Strategies.StrategyOptions +end + +function FakeIntegrator() + return FakeIntegrator(CTSolvers.Strategies.StrategyOptions()) +end + +# Implement CTSolvers strategy contract +function CTSolvers.Strategies.id(::Type{FakeIntegrator}) + return :fake_integrator +end + +function CTSolvers.Strategies.metadata(::Type{FakeIntegrator}) + return CTSolvers.Strategies.StrategyMetadata() +end + +function CTSolvers.Strategies.options(integ::FakeIntegrator) + return integ.options +end + +function CTSolvers.Strategies.describe(::Type{FakeIntegrator}) + return "Fake integrator for testing" +end + +function (integ::FakeIntegrator)(ode_problem) + return :fake_solution +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_flow_module() + Test.@testset "Flows Module Exports" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + Test.@testset "AbstractFlow is exported" begin + Test.@test isdefined(Flows, :AbstractFlow) + Test.@test isabstracttype(Flows.AbstractFlow) + end + end + + # ==================================================================== + # Concrete Types + # ==================================================================== + + Test.@testset "Concrete Types" begin + Test.@testset "StateFlow is exported" begin + Test.@test isdefined(Flows, :StateFlow) + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test flow isa Flows.StateFlow + Test.@test flow isa Flows.AbstractFlow + end + + Test.@testset "StateFlow constructor is exported" begin + Test.@test isdefined(Flows, :StateFlow) + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test flow isa Flows.StateFlow + Test.@test flow isa Flows.AbstractFlow + end + end + + # ==================================================================== + # Accessor Functions + # ==================================================================== + + Test.@testset "Accessor Functions" begin + Test.@testset "system is exported" begin + Test.@test isdefined(Flows, :system) + end + + Test.@testset "system returns the associated system" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + retrieved_sys = Flows.system(flow) + Test.@test retrieved_sys === sys + end + + Test.@testset "integrator is exported" begin + Test.@test isdefined(Flows, :integrator) + end + + Test.@testset "integrator returns the associated integrator" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + retrieved_integ = Flows.integrator(flow) + Test.@test retrieved_integ === integ + end + end + + # ==================================================================== + # Trait Support + # ==================================================================== + + Test.@testset "Trait Support" begin + Test.@testset "StateFlow has time dependence trait" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test Traits.has_time_dependence_trait(flow) + end + + Test.@testset "StateFlow has variable dependence trait" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test Traits.has_variable_dependence_trait(flow) + end + + Test.@testset "time_dependence delegates to system" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test Traits.time_dependence(flow) === Traits.Autonomous + end + + Test.@testset "variable_dependence delegates to system" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test Traits.variable_dependence(flow) === Traits.Fixed + end + end + + # ==================================================================== + # Type Hierarchy Verification + # ==================================================================== + + Test.@testset "Type Hierarchy" begin + Test.@testset "StateFlow is a subtype of AbstractFlow" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test flow isa Flows.AbstractFlow + end + + Test.@testset "Concrete StateFlow instances are AbstractFlow" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + Test.@test flow isa Flows.AbstractFlow + Test.@test flow isa Flows.StateFlow + end + end + + # ==================================================================== + # Display Methods + # ==================================================================== + + Test.@testset "Display Methods" begin + Test.@testset "tree-style display works" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + # Just verify it doesn't throw + io = IOBuffer() + show(io, MIME("text/plain"), flow) + output = String(take!(io)) + Test.@test !isempty(output) + end + + Test.@testset "compact display works" begin + sys = FakeSystem(2, 2) + integ = FakeIntegrator() + flow = Flows.StateFlow(sys, integ) + # Just verify it doesn't throw + io = IOBuffer() + show(io, flow) + output = String(take!(io)) + Test.@test !isempty(output) + end + end + end +end + +end # module TestFlowModule + +# CRITICAL: Redefine in outer scope for TestRunner +test_flow_module() = TestFlowModule.test_flow_module() \ No newline at end of file diff --git a/test/suite/flows/test_flow_routing.jl b/test/suite/flows/test_flow_routing.jl new file mode 100644 index 00000000..1ab6a48d --- /dev/null +++ b/test/suite/flows/test_flow_routing.jl @@ -0,0 +1,144 @@ +""" +Unit and integration tests for flow routing via CTSolvers. +""" + +module TestFlowRouting + +import Test +import CTBase.Exceptions +import CTFlows.Flows +import CTFlows.Differentiation +import CTFlows.Integrators +import CTFlows.Data +import CTFlows.Systems +import CTFlows.Traits +import CTFlows.Common +import CTSolvers +import ADTypes +using OrdinaryDiffEqTsit5 + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake Hamiltonian for Testing (at module top-level) +# ============================================================================== + +const _TEST_H = Data.Hamiltonian( + (t, x, p, v) -> 0.5 * (x[1]^2 + p[1]^2); + is_autonomous=true, is_variable=false +) + +function test_flow_routing() + Test.@testset "Flow Routing Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Registry and Families + # ==================================================================== + + Test.@testset "Unit: _flow_families" begin + families = Flows._flow_families() + Test.@test haskey(families, :backend) + Test.@test haskey(families, :integrator) + Test.@test families.backend === Differentiation.AbstractADBackend + Test.@test families.integrator === Integrators.AbstractIntegrator + end + + Test.@testset "Unit: _FLOW_DESCRIPTION" begin + Test.@test Flows._FLOW_DESCRIPTION === (:di, :sciml) + end + + Test.@testset "Unit: flow_registry" begin + registry = Flows.flow_registry() + Test.@test registry isa CTSolvers.Strategies.StrategyRegistry + # Check that strategies are registered + backend_ids = CTSolvers.Strategies.strategy_ids(Differentiation.AbstractADBackend, registry) + Test.@test :di in backend_ids + integrator_ids = CTSolvers.Strategies.strategy_ids(Integrators.AbstractIntegrator, registry) + Test.@test :sciml in integrator_ids + end + + # ==================================================================== + # UNIT TESTS - Option Routing + # ==================================================================== + + Test.@testset "Unit: _route_flow_options โ€” empty kwargs" begin + routed = Flows._route_flow_options(NamedTuple()) + Test.@test haskey(routed, :strategies) + Test.@test haskey(routed.strategies, :backend) + Test.@test haskey(routed.strategies, :integrator) + Test.@test isempty(routed.strategies.backend) + Test.@test isempty(routed.strategies.integrator) + end + + Test.@testset "Unit: _route_flow_options โ€” integrator option" begin + routed = Flows._route_flow_options((; reltol=1e-10)) + Test.@test haskey(routed.strategies, :integrator) + Test.@test haskey(routed.strategies.integrator, :reltol) + Test.@test routed.strategies.integrator.reltol == 1e-10 + end + + Test.@testset "Unit: _route_flow_options โ€” backend alias" begin + routed = Flows._route_flow_options((; ad_backend=ADTypes.AutoForwardDiff())) + Test.@test haskey(routed.strategies, :backend) + Test.@test haskey(routed.strategies.backend, :ad_backend) + Test.@test routed.strategies.backend.ad_backend === ADTypes.AutoForwardDiff() + end + + # ==================================================================== + # ERROR TESTS + # ==================================================================== + + Test.@testset "Error: _route_flow_options โ€” unknown option" begin + Test.@test_throws Exceptions.IncorrectArgument Flows._route_flow_options((; unknown_option=42)) + end + + # ==================================================================== + # UNIT TESTS - Component Building + # ==================================================================== + + Test.@testset "Unit: _build_flow_components โ€” defaults" begin + routed = Flows._route_flow_options(NamedTuple()) + components = Flows._build_flow_components(routed) + Test.@test haskey(components, :backend) + Test.@test haskey(components, :integrator) + Test.@test components.backend isa Differentiation.DifferentiationInterface + Test.@test components.integrator isa Integrators.SciML + end + + # ==================================================================== + # INTEGRATION TESTS + # ==================================================================== + + Test.@testset "Integration: Flow(h) โ€” end-to-end" begin + flow = Flows.Flow(_TEST_H) + Test.@test flow isa Flows.HamiltonianFlow + Test.@test flow isa Flows.AbstractFlow + Test.@test Flows.system(flow) isa Systems.AbstractHamiltonianSystem + Test.@test Flows.integrator(flow) isa Integrators.AbstractIntegrator + end + + + Test.@testset "Integration: Flow(h; reltol=1e-9)" begin + flow = Flows.Flow(_TEST_H; reltol=1e-9) + Test.@test flow isa Flows.HamiltonianFlow + Test.@test Flows.integrator(flow) isa Integrators.SciML + end + + # ==================================================================== + # REGRESSION TESTS + # ==================================================================== + + Test.@testset "Regression: Flow(h) sans kwargs" begin + flow = Flows.Flow(_TEST_H) + Test.@test flow isa Flows.HamiltonianFlow + Test.@test Flows.system(flow) isa Systems.AbstractHamiltonianSystem + end + + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_flow_routing() = TestFlowRouting.test_flow_routing() diff --git a/test/suite/flows/test_variable_costate_flows.jl b/test/suite/flows/test_variable_costate_flows.jl new file mode 100644 index 00000000..5e533f7d --- /dev/null +++ b/test/suite/flows/test_variable_costate_flows.jl @@ -0,0 +1,420 @@ +""" +Test suite for variable costate integration (augmented Hamiltonian systems). + +Tests the `variable_costate=true` kwarg on Hamiltonian flows, which enables +integration of the augmented state `[x; p; pv]` where `pv` is the costate of the variable. +""" +module TestVariableCostateFlows + +import Test +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Configs: Configs +import CTFlows.Traits: Traits +import CTFlows.Systems: Systems +import CTFlows.Flows: Flows +import CTFlows.Solutions: Solutions +import CTFlows.Integrators: Integrators +import CTFlows.Differentiation: Differentiation +import CTFlows.Data: Data + +using SciMLBase: SciMLBase +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector, MVector +using ADTypes: ADTypes +using DifferentiationInterface: DifferentiationInterface as DI + +# Load extensions for testing +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsDifferentiationInterface = Base.get_extension(CTFlows, :CTFlowsDifferentiationInterface) + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const ATOL = 1e-3 + +# ============================================================================= +# Fake types for testing +# ============================================================================= + +# Fake integration result +struct FakeIntegrationResult <: Integrators.AbstractIntegrationResult + u_final::Vector{Float64} +end + +Integrators.final_state(r::FakeIntegrationResult) = r.u_final + +# Fake AD backend for linear Hamiltonian: H = sum(p^2) / (2 * sum(v)) +struct FakeVariableHarmonicADBackend <: Differentiation.AbstractADBackend end + +function Differentiation.hamiltonian_gradient(backend::FakeVariableHarmonicADBackend, h, t, x, p, v, cache) + # โˆ‚H/โˆ‚x = 0, โˆ‚H/โˆ‚p = p / sum(v) + sv = sum(v) + return (zero(x), p ./ sv) +end + +function Differentiation.variable_gradient(backend::FakeVariableHarmonicADBackend, h, t, x, p, v, cache) + # โˆ‚H/โˆ‚v = -sum(p^2) / (2 * sum(v)^2) * ones(length(v)) + sv = sum(v) + sp2 = sum(abs2, p) + return fill(-sp2 / (2 * sv^2), length(v)) +end + +function Differentiation.prepare_cache(backend::FakeVariableHarmonicADBackend, h, typical_t, typical_x, typical_p, typical_v) + return nothing +end + +# ============================================================================= +# Reference systems for numerical testing +# ============================================================================= + +# Linear Hamiltonian: H = sum(p^2) / (2 * sum(v)) +# Equations: dx = p / sum(v), dp = 0, dpv = -sum(p^2) / (2 * sum(v)^2) +# Analytical solution: x(t) = x0 + p0 * (t-t0) / sum(v), p(t) = p0, pv(t) = pv0 - sum(p0^2) * (t-t0) / (2 * sum(v)^2) +H_LINEAR(x, p, v) = sum(abs2, p) / (2 * sum(v)) +function solve_linear(t, t0, x0, p0, v) + pv0 = Common.scalarize(zeros(length(v)), v) + dt = t - t0 + sv = sum(v) + x = x0 .+ p0 .* (dt / sv) + p = p0 + pv = pv0 .+ sum(abs2, p0) / (2 * sv^2) .* dt + return x, p, pv +end +const H_LINEAR_VAR = Data.Hamiltonian(H_LINEAR; is_autonomous=true, is_variable=true) +const BACKEND_FAKE = FakeVariableHarmonicADBackend() +const HSYS_FAKE = Systems.HamiltonianSystem(H_LINEAR_VAR, BACKEND_FAKE) + +# DifferentiationInterface backends +const DI_BACKEND_CACHED = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=true) +const DI_BACKEND_UNCACHED = Differentiation.DifferentiationInterface(; ad_backend=ADTypes.AutoForwardDiff(), prepare_cache=false) +const HSYS_DI_CACHED = Systems.HamiltonianSystem(H_LINEAR_VAR, DI_BACKEND_CACHED) +const HSYS_DI_UNCACHED = Systems.HamiltonianSystem(H_LINEAR_VAR, DI_BACKEND_UNCACHED) + +# Scalar-only Hamiltonian with variable: H = xยณ/3 - log(p) + vยณ/3 +# This will fail if x, p, or v are treated as vectors +# Gradients: โˆ‚H/โˆ‚x = x*x, โˆ‚H/โˆ‚p = -1/p, โˆ‚H/โˆ‚v = v*v +H_SCALAR_VAR(x, p, v) = x^3/3 - log(p) + v^3/3 +const H_SCALAR_VAR_H = Data.Hamiltonian(H_SCALAR_VAR; is_autonomous=true, is_variable=true) +const HSYS_SCALAR_VAR = Systems.HamiltonianSystem(H_SCALAR_VAR_H, DI_BACKEND_CACHED) + +const INTEG = Integrators.SciML() + +function test_variable_costate_flows() + Test.@testset "Variable Costate Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS + # ==================================================================== + + Test.@testset "Unit: _aug_split_solution" begin + u = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + x0 = [1.0, 2.0] # n = 2 + pv0 = [5.0, 6.0, 7.0, 8.0] + + x, p, pv = Solutions._aug_split_solution(u, x0, pv0) + + Test.@test x == [1.0, 2.0] + Test.@test p == [3.0, 4.0] + Test.@test pv == [5.0, 6.0, 7.0, 8.0] + end + + Test.@testset "Unit: build_solution AugmentedHamiltonianTrait" begin + u_final = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + x0 = [1.0, 2.0] # n = 2 + p0 = [3.0, 4.0] + pv0 = [5.0, 6.0, 7.0, 8.0] + config = Configs.AugmentedHamiltonianPointConfig(0, x0, p0, pv0, 1) + result = FakeIntegrationResult(u_final) + + xf, pf, pvf = Solutions.build_solution( + Configs.mode_trait(config), + Configs.content_trait(config), + config, + result, + ) + + Test.@test xf == [1.0, 2.0] + Test.@test pf == [3.0, 4.0] + Test.@test pvf == [5.0, 6.0, 7.0, 8.0] + end + + Test.@testset "Unit: trait type hierarchy" begin + Test.@test Traits.SupportsVariableCostate <: Traits.AbstractVariableCostateCapability + Test.@test Traits.NoVariableCostate <: Traits.AbstractVariableCostateCapability + Test.@test Traits.AbstractVariableCostateCapability <: Traits.AbstractTrait + end + + Test.@testset "Unit: variable_costate_trait default" begin + Test.@test Traits.variable_costate_trait(42) === Traits.NoVariableCostate + Test.@test Traits.variable_costate_trait("anything") === Traits.NoVariableCostate + Test.@test Traits.variable_costate_trait(nothing) === Traits.NoVariableCostate + end + + Test.@testset "Unit: ad_trait on flows" begin + # Default implementation on AbstractFlow returns WithoutAD + Test.@test Traits.ad_trait("fake_flow") === Traits.WithoutAD + Test.@test Traits.ad_trait(42) === Traits.WithoutAD + end + + # ==================================================================== + # INTEGRATION TESTS + # ==================================================================== + + Test.@testset "Integration: variable_costate_trait on systems" begin + # Fixed HamiltonianSystem -> NoVariableCostate + h_fixed = Data.Hamiltonian((x, p) -> 0.5*(sum(abs2, x) + sum(abs2, p)); is_autonomous=true, is_variable=false) + sys_fixed = Systems.HamiltonianSystem(h_fixed, BACKEND_FAKE) + Test.@test Traits.variable_costate_trait(sys_fixed) === Traits.NoVariableCostate + + # NonFixed HamiltonianSystem -> SupportsVariableCostate + Test.@test Traits.variable_costate_trait(HSYS_FAKE) === Traits.SupportsVariableCostate + Test.@test Traits.variable_costate_trait(HSYS_FAKE) === Traits.SupportsVariableCostate + + # DI backends also support variable costate + Test.@test Traits.variable_costate_trait(HSYS_DI_CACHED) === Traits.SupportsVariableCostate + Test.@test Traits.variable_costate_trait(HSYS_DI_UNCACHED) === Traits.SupportsVariableCostate + end + + Test.@testset "Integration: ad_trait on systems" begin + # HamiltonianSystem always has WithAD + Test.@test Traits.ad_trait(HSYS_FAKE) === Traits.WithAD + Test.@test Traits.ad_trait(HSYS_FAKE) === Traits.WithAD + + # DI backends also have WithAD + Test.@test Traits.ad_trait(HSYS_DI_CACHED) === Traits.WithAD + end + + Test.@testset "Integration: FakeADBackend variable_costate flow" begin + # Linear Hamiltonian: H = sum(p^2) / (2 * sum(v)) + # Equations: dx = p / sum(v), dp = 0, dpv = -sum(p^2) / (2 * sum(v)^2) + # Analytical: x(t) = x0 + p0 * (t-t0) / sum(v), p(t) = p0, pv(t) = -sum(p0^2) * (t-t0) / (2 * sum(v)^2) + + Test.@testset "scalar with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = 3.0 + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + + # Analytical solution + xf_ref, pf_ref, pvf_ref = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test pvf isa Real + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + Test.@test pvf โ‰ˆ pvf_ref atol=ATOL + end + + Test.@testset "scalar with variable_costate=false (default)" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = 3.0 + xf, pf = hflow(t0, x0, p0, tf; variable=v, variable_costate=false) + + # Analytical solution (without pv) + xf_ref, pf_ref, _ = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + end + + Test.@testset "vector with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + t0, tf = 0.0, 1.0 + x0 = [1.0, 2.0] + p0 = [3.0, 4.0] + v = [5.0, 6.0] + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + + # Analytical solution + xf_ref, pf_ref, pvf_ref = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa AbstractVector && length(xf) == 2 + Test.@test pf isa AbstractVector && length(pf) == 2 + Test.@test pvf isa AbstractVector && length(pvf) == 2 + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + Test.@test pvf โ‰ˆ pvf_ref atol=ATOL + end + + Test.@testset "SVector with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + t0, tf = 0.0, 1.0 + x0 = SA[1.0, 2.0] + p0 = SA[3.0, 4.0] + v = SA[5.0, 6.0] + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + + # Analytical solution + xf_ref, pf_ref, pvf_ref = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa AbstractVector + Test.@test pf isa AbstractVector + Test.@test pvf isa AbstractVector + Test.@test length(xf) == 2 + Test.@test length(pf) == 2 + Test.@test length(pvf) == 2 + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + Test.@test pvf โ‰ˆ pvf_ref atol=ATOL + end + end + + Test.@testset "Integration: DI variable_costate flow" begin + Test.@testset "scalar cached with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_DI_CACHED, INTEG) + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = 3.0 + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + + # Analytical solution + xf_ref, pf_ref, pvf_ref = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test pvf isa Real + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + Test.@test pvf โ‰ˆ pvf_ref atol=ATOL + end + + Test.@testset "scalar uncached with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_DI_UNCACHED, INTEG) + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = [3.0] # TODO: should work with a scalar. + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + + # Analytical solution + xf_ref, pf_ref, pvf_ref = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa Real + Test.@test pf isa Real + # Test.@test pvf isa Real + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + Test.@test pvf โ‰ˆ pvf_ref atol=ATOL + end + + Test.@testset "vector cached with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_DI_CACHED, INTEG) + t0, tf = 0.0, 1.0 + x0 = [1.0, 2.0] + p0 = [3.0, 4.0] + v = [5.0, 6.0] + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + + # Analytical solution + xf_ref, pf_ref, pvf_ref = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa AbstractVector && length(xf) == 2 + Test.@test pf isa AbstractVector && length(pf) == 2 + Test.@test pvf isa AbstractVector && length(pvf) == 2 + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + Test.@test pvf โ‰ˆ pvf_ref atol=ATOL + end + + Test.@testset "comparison FakeADBackend vs DI" begin + # Both should give the same numerical result + hflow_fake = Flows.build_flow(HSYS_FAKE, INTEG) + hflow_di = Flows.build_flow(HSYS_DI_CACHED, INTEG) + + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = 3.0 + + xf_f, pf_f, pvf_f = hflow_fake(t0, x0, p0, tf; variable=v, variable_costate=true) + xf_d, pf_d, pvf_d = hflow_di(t0, x0, p0, tf; variable=v, variable_costate=true) + + Test.@test xf_f โ‰ˆ xf_d atol=ATOL + Test.@test pf_f โ‰ˆ pf_d atol=ATOL + Test.@test pvf_f โ‰ˆ pvf_d atol=ATOL + end + end + + Test.@testset "Integration: regression tests" begin + Test.@testset "variable_costate=false default unchanged" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = 3.0 + xf, pf = hflow(t0, x0, p0, tf; variable=v) + + # Analytical solution (without pv) + xf_ref, pf_ref, _ = solve_linear(tf, t0, x0, p0, v) + + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test xf โ‰ˆ xf_ref atol=ATOL + Test.@test pf โ‰ˆ pf_ref atol=ATOL + end + end + + # ==================================================================== + # SCALAR-ONLY TESTS - verify scalar dispatch (x*x + p*p + v*v) + # ==================================================================== + + Test.@testset "Scalar-only Hamiltonian with variable (xยณ/3 - log(p) + vยณ/3)" begin + Test.@testset "scalar x0, p0, v with variable_costate=true" begin + hflow = Flows.build_flow(HSYS_SCALAR_VAR, INTEG) + # H = xยณ/3 - log(p) + vยณ/3 โ†’ โˆ‚H/โˆ‚x = xยฒ, โˆ‚H/โˆ‚p = -1/p, โˆ‚H/โˆ‚v = vยฒ + # Equations: แบ‹ = 1/p, แน— = -xยฒ, แน—v = -vยฒ + # This will fail if x, p, or v are treated as vectors (can't do x*x, 1/p, v*v on vectors) + t0, tf = 0.0, 0.1 + x0, p0, v = 0.5, 1.0, 0.5 + xf, pf, pvf = hflow(t0, x0, p0, tf; variable=v, variable_costate=true) + # Just verify it integrates without error and returns scalars + Test.@test xf isa Real + Test.@test pf isa Real + Test.@test pvf isa Real + Test.@test pf > 0 # p should stay positive + end + end + + # ==================================================================== + # ERROR TESTS + # ==================================================================== + + Test.@testset "Error: variable_costate=true on Fixed system" begin + h_fixed = Data.Hamiltonian((x, p) -> 0.5*(sum(abs2, x) + sum(abs2, p)); is_autonomous=true, is_variable=false) + sys_fixed = Systems.HamiltonianSystem(h_fixed, BACKEND_FAKE) + hflow = Flows.build_flow(sys_fixed, INTEG) + Test.@test_throws Exceptions.PreconditionError hflow(0.0, 1.0, 0.0, 1.0; variable_costate=true) + end + + Test.@testset "Error: variable_costate=true without variable on NonFixed" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + Test.@test_throws Exceptions.PreconditionError hflow(0.0, 1.0, 0.0, 1.0; variable_costate=true) + end + + Test.@testset "Error: HamiltonianVectorFieldSystem with variable_costate" begin + # HamiltonianVectorFieldSystem has NoVariableCostate (Fixed only) + hvf = Data.HamiltonianVectorField((x, p) -> (p, -x); is_autonomous=true, is_variable=false) + sys_hvf = Systems.HamiltonianVectorFieldSystem(hvf) + hflow = Flows.build_flow(sys_hvf, INTEG) + Test.@test_throws Exceptions.PreconditionError hflow(0.0, 1.0, 0.0, 1.0; variable_costate=true) + end + + Test.@testset "Error: variable_costate=true with trajectory config" begin + hflow = Flows.build_flow(HSYS_FAKE, INTEG) + t0, tf = 0.0, 1.0 + x0, p0 = 1.0, 2.0 + v = 3.0 + Test.@test_throws Exceptions.PreconditionError hflow((t0, tf), x0, p0; variable=v, variable_costate=true) + end + + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_variable_costate_flows() = TestVariableCostateFlows.test_variable_costate_flows() diff --git a/test/suite/integrators/test_abstract_integrator.jl b/test/suite/integrators/test_abstract_integrator.jl new file mode 100644 index 00000000..1334fb5b --- /dev/null +++ b/test/suite/integrators/test_abstract_integrator.jl @@ -0,0 +1,155 @@ +module TestAbstractIntegrator + +import Test +import CTBase.Exceptions +import CTFlows.Integrators +import CTFlows.Systems +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Traits +import CTSolvers: CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake system for testing the AbstractIntegrator contract. +""" +struct FakeSystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + state_dim::Int +end + +""" +Fake integration result for testing merge stub behavior. +""" +struct FakeIntegrationResult <: Integrators.AbstractIntegrationResult + data::Vector{Float64} +end + +Integrators.final_state(r::FakeIntegrationResult) = r.data +Integrators.times(r::FakeIntegrationResult) = collect(eachindex(r.data)) +Integrators.evaluate_at(r::FakeIntegrationResult, t::Real) = r.data[Int(clamp(round(t), 1, length(r.data)))] + +""" +Fake integrator for testing the AbstractIntegrator contract. + +This minimal implementation provides all three required callable signatures +to test routing and default behavior without full integrator complexity. +""" +struct FakeIntegrator <: Integrators.AbstractIntegrator + options::CTSolvers.Strategies.StrategyOptions +end + +function FakeIntegrator() + return FakeIntegrator(CTSolvers.Strategies.StrategyOptions()) +end + +# Implement the three required callable signatures +function Integrators.build_problem(integ::FakeIntegrator, system::Systems.AbstractSystem, config::Configs.AbstractConfig; variable, cache) + p = Common.ODEParameters(variable) + return :fake_ode_problem +end + +function Integrators.build_options(integ::FakeIntegrator, config::Union{Configs.AbstractConfig, Nothing}) + return Dict{Symbol,Any}() +end + +function Integrators.solve_problem(integ::FakeIntegrator, prob, options::Dict{Symbol,Any}) + return :fake_ode_solution +end + +""" +Minimal integrator that does not implement the contract (for error testing). +""" +struct MinimalIntegrator <: Integrators.AbstractIntegrator + options::CTSolvers.Strategies.StrategyOptions +end + +function MinimalIntegrator() + return MinimalIntegrator(CTSolvers.Strategies.StrategyOptions()) +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_integrator() + Test.@testset "Abstract ODE Integrator Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + integ = FakeIntegrator() + Test.@test integ isa Integrators.AbstractIntegrator + Test.@test integ isa CTSolvers.Strategies.AbstractStrategy + + minimal = MinimalIntegrator() + Test.@test minimal isa Integrators.AbstractIntegrator + end + + # ==================================================================== + # UNIT TESTS - Contract Implementation + # ==================================================================== + + Test.@testset "Contract Implementation" begin + integ = FakeIntegrator() + sys = FakeSystem(2) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + Test.@testset "Problem building signature" begin + result = Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + Test.@test result === :fake_ode_problem + end + + Test.@testset "Integration signature" begin + result = Integrators.solve_problem(integ, :fake_prob, Dict{Symbol,Any}()) + Test.@test result === :fake_ode_solution + end + + Test.@testset "build_options signature" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + opts = Integrators.build_options(integ, config) + Test.@test opts isa Dict{Symbol,Any} + end + end + + # ==================================================================== + # UNIT TESTS - NotImplemented Errors + # ==================================================================== + + Test.@testset "NotImplemented Errors" begin + integ = MinimalIntegrator() + sys = FakeSystem(2) + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + + Test.@testset "Problem building throws NotImplemented" begin + Test.@test_throws Exceptions.NotImplemented Integrators.build_problem(integ, sys, config; variable=nothing, cache=nothing) + end + + Test.@testset "Integration throws NotImplemented" begin + Test.@test_throws Exceptions.NotImplemented Integrators.solve_problem(integ, :fake_prob, Dict{Symbol,Any}()) + end + + Test.@testset "build_options throws NotImplemented" begin + config = Configs.StatePointConfig(0.0, [1.0, 0.0], 1.0) + Test.@test_throws Exceptions.NotImplemented Integrators.build_options(integ, config) + end + + Test.@testset "merge throws NotImplemented" begin + result = FakeIntegrationResult([1.0, 2.0]) + Test.@test_throws Exceptions.NotImplemented Integrators.merge([result]) + Test.@test_throws Exceptions.NotImplemented Integrators.merge(FakeIntegrationResult[]) + end + end + end +end + +end # module + +test_abstract_integrator() = TestAbstractIntegrator.test_abstract_integrator() diff --git a/test/suite/integrators/test_building_integrators.jl b/test/suite/integrators/test_building_integrators.jl new file mode 100644 index 00000000..9870a534 --- /dev/null +++ b/test/suite/integrators/test_building_integrators.jl @@ -0,0 +1,42 @@ +module TestBuildingIntegrators + +import Test +import CTBase.Exceptions +import CTFlows.Integrators +import CTFlows.Integrators: SciML +using OrdinaryDiffEqTsit5 + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_building_integrators() + Test.@testset "Integrator Building Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - build_integrator + # ==================================================================== + + Test.@testset "build_integrator" begin + + Test.@testset "builds SciML integrator" begin + # This will throw ExtensionError if CTFlowsSciML is not loaded, + # but at least it calls the right function + result = Integrators.build_integrator() + Test.@test result isa Integrators.SciML + end + + Test.@testset "forwards keyword options" begin + result = Integrators.build_integrator(reltol=1e-10) + Test.@test result isa Integrators.SciML + end + end + end +end + +end # module + +test_building_integrators() = TestBuildingIntegrators.test_building_integrators() \ No newline at end of file diff --git a/test/suite/integrators/test_integration_result.jl b/test/suite/integrators/test_integration_result.jl new file mode 100644 index 00000000..d822a068 --- /dev/null +++ b/test/suite/integrators/test_integration_result.jl @@ -0,0 +1,73 @@ +module TestIntegrationResult + +import Test +import CTFlows.Integrators +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake type for stub testing +# ============================================================================== + +struct FakeResult <: Integrators.AbstractIntegrationResult end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_integration_result() + Test.@testset "Integration Result Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - AbstractIntegrationResult + # ==================================================================== + + Test.@testset "AbstractIntegrationResult" begin + Test.@testset "final_state throws NotImplemented on abstract type" begin + result = FakeResult() + + Test.@test_throws Exceptions.NotImplemented Integrators.final_state(result) + end + + Test.@testset "times throws NotImplemented on abstract type" begin + result = FakeResult() + + Test.@test_throws Exceptions.NotImplemented Integrators.times(result) + end + + Test.@testset "evaluate_at throws NotImplemented on abstract type" begin + result = FakeResult() + + Test.@test_throws Exceptions.NotImplemented Integrators.evaluate_at(result, 0.0) + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "AbstractIntegrationResult is exported" begin + Test.@test isdefined(Integrators, :AbstractIntegrationResult) + end + + Test.@testset "final_state is exported" begin + Test.@test isdefined(Integrators, :final_state) + end + + Test.@testset "times is exported" begin + Test.@test isdefined(Integrators, :times) + end + + Test.@testset "evaluate_at is exported" begin + Test.@test isdefined(Integrators, :evaluate_at) + end + end + end +end + +end # module + +test_integration_result() = TestIntegrationResult.test_integration_result() \ No newline at end of file diff --git a/test/suite/integrators/test_integrators_module.jl b/test/suite/integrators/test_integrators_module.jl new file mode 100644 index 00000000..d5ffd574 --- /dev/null +++ b/test/suite/integrators/test_integrators_module.jl @@ -0,0 +1,60 @@ +module TestIntegratorsModule + +import Test +import CTFlows.Integrators + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_integrators_module() + Test.@testset "Integrators Module Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "AbstractIntegrator is exported" begin + Test.@test isdefined(Integrators, :AbstractIntegrator) + end + + Test.@testset "SciML is exported" begin + Test.@test isdefined(Integrators, :SciML) + end + + Test.@testset "SciMLTag is exported" begin + Test.@test isdefined(Integrators, :SciMLTag) + end + + Test.@testset "build_sciml_integrator is exported" begin + Test.@test isdefined(Integrators, :build_sciml_integrator) + end + + Test.@testset "build_integrator is exported" begin + Test.@test isdefined(Integrators, :build_integrator) + end + end + + # ==================================================================== + # UNIT TESTS - Module Loading + # ==================================================================== + + Test.@testset "Module Loading" begin + Test.@testset "Integrators module exists" begin + Test.@test @isdefined(Integrators) + end + + Test.@testset "Integrators is a Module" begin + Test.@test Integrators isa Module + end + end + end +end + +end # module + +test_integrators_module() = TestIntegratorsModule.test_integrators_module() \ No newline at end of file diff --git a/test/suite/integrators/test_sciml.jl b/test/suite/integrators/test_sciml.jl new file mode 100644 index 00000000..d6681b7b --- /dev/null +++ b/test/suite/integrators/test_sciml.jl @@ -0,0 +1,130 @@ +module TestSciML + +import Test +import CTBase.Exceptions +import CTFlows.Integrators +import CTFlows.Common +import CTSolvers: CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing stubs +# ============================================================================== + +""" +Fake SciML tag for testing stub methods on AbstractTag. +""" +struct FakeSciMLTag <: Common.AbstractTag end + +""" +Fake SciML integrator for testing stub methods on AbstractSciMLIntegrator. +""" +struct FakeSciMLIntegrator <: Integrators.AbstractSciMLIntegrator + data::String +end + +""" +Fake SciML strategy for testing constructor stub. +""" +struct FakeSciML <: Integrators.AbstractSciMLIntegrator + options::CTSolvers.Strategies.StrategyOptions +end + +# Fake constructor that throws ExtensionError +function FakeSciML(; kwargs...) + throw( + Exceptions.ExtensionError( + :SciML; + message="SciML extension not loaded", + feature="SciML integrator", + context="FakeSciML constructor - extension availability check", + ), + ) +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_sciml() + Test.@testset "SciML Integrator Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Type Hierarchy + # ==================================================================== + + Test.@testset "Type Hierarchy" begin + Test.@test Integrators.SciMLTag <: Common.AbstractTag + end + + # ==================================================================== + # UNIT TESTS - AbstractStrategy Contract + # ==================================================================== + + Test.@testset "AbstractStrategy Contract" begin + + Test.@testset "id returns :sciml" begin + Test.@test CTSolvers.Strategies.id(Integrators.SciML) === :sciml + end + + Test.@testset "description returns non-empty string" begin + desc = CTSolvers.Strategies.description(Integrators.SciML) + Test.@test desc isa String + Test.@test !isempty(desc) + Test.@test occursin("SciML", desc) + end + end + + # ==================================================================== + # UNIT TESTS - Extension Error Stubs + # ==================================================================== + + Test.@testset "Extension Error Stubs" begin + + Test.@testset "metadata throws ExtensionError" begin + fake_integrator = FakeSciMLIntegrator("test") + Test.@test_throws Exceptions.ExtensionError CTSolvers.Strategies.metadata(typeof(fake_integrator)) + end + + Test.@testset "build_sciml_integrator throws ExtensionError" begin + Test.@test_throws Exceptions.ExtensionError Integrators.build_sciml_integrator(FakeSciMLTag) + end + end + + # ==================================================================== + # UNIT TESTS - Error Messages + # ==================================================================== + + Test.@testset "Error Messages" begin + + Test.@testset "constructor error mentions SciML" begin + try + FakeSciML() + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.ExtensionError + msg = sprint(showerror, err) + Test.@test occursin("SciML", msg) + end + end + + Test.@testset "metadata error mentions OrdinaryDiffEqTsit5" begin + fake_integrator = FakeSciMLIntegrator("test") + try + CTSolvers.Strategies.metadata(typeof(fake_integrator)) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.ExtensionError + msg = sprint(showerror, err) + Test.@test occursin("OrdinaryDiffEqTsit5", msg) + end + end + end + end +end + +end # module + +test_sciml() = TestSciML.test_sciml() \ No newline at end of file diff --git a/test/suite/meta/test_aqua.jl b/test/suite/meta/test_aqua.jl new file mode 100644 index 00000000..53647b39 --- /dev/null +++ b/test/suite/meta/test_aqua.jl @@ -0,0 +1,33 @@ +module TestAqua + +import Aqua +import Test +import CTFlows + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_aqua() + Test.@testset "Aqua Quality Checks" verbose=VERBOSE showtiming=SHOWTIMING begin + + Test.@testset "Aqua" begin + Aqua.test_all(CTFlows; ambiguities=false, unbound_args=false, undefined_exports=false) + end + + Test.@testset "Ambiguities" begin + Aqua.test_ambiguities(CTFlows) + end + + Test.@testset "Unbound Args" begin + Aqua.test_unbound_args(CTFlows) + end + + Test.@testset "Undefined Exports" begin + Aqua.test_undefined_exports(CTFlows) + end + end +end + +end # module + +test_aqua() = TestAqua.test_aqua() diff --git a/test/suite/multiphase/test_calling_multiphase.jl b/test/suite/multiphase/test_calling_multiphase.jl new file mode 100644 index 00000000..c6b028e5 --- /dev/null +++ b/test/suite/multiphase/test_calling_multiphase.jl @@ -0,0 +1,552 @@ +module TestCallingMultiphase + +import Test +import CTFlows.MultiPhase +import CTFlows.Systems +import CTFlows.Integrators +import CTFlows.Flows +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Traits +import CTFlows.Solutions + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing (testing-creation.md ยง1) +# ============================================================================== + +struct FakeStateSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +function Systems.rhs(sys::FakeStateSystem) + return (du, u, p, t) -> du .= sys.data .* u +end + +struct FakeHamiltonianSystem <: Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithoutAD} + data::Vector{Float64} +end + +function Systems.rhs(sys::FakeHamiltonianSystem) + return (dz, z, p, t) -> dz .= sys.data .* z +end + +struct FakeIntegrator <: Integrators.AbstractIntegrator + result::Any +end + +struct FakeHamiltonianIntegrator <: Integrators.AbstractIntegrator + result::Any +end + +# Mock integrator for testing calling.jl functions +struct MockIntegrator <: Integrators.AbstractIntegrator + multiplier::Float64 +end + +# Mock ODE problem type +struct MockODEProblem + u0::Vector{Float64} + tspan::Tuple{Float64, Float64} +end + +# Mock integration result type +struct MockIntegrationResult <: Integrators.AbstractIntegrationResult + u::Vector{Float64} + t::Vector{Float64} +end + +import CTSolvers.Strategies +import CTSolvers.Options + +Strategies.id(::Type{FakeIntegrator}) = :fake_integrator +Strategies.metadata(::Type{FakeIntegrator}) = Strategies.StrategyMetadata() +Strategies.options(integ::FakeIntegrator) = Options.StrategyOptions() + +Strategies.id(::Type{FakeHamiltonianIntegrator}) = :fake_ham_integrator +Strategies.metadata(::Type{FakeHamiltonianIntegrator}) = Strategies.StrategyMetadata() +Strategies.options(integ::FakeHamiltonianIntegrator) = Options.StrategyOptions() + +Strategies.id(::Type{MockIntegrator}) = :mock_integrator +Strategies.metadata(::Type{MockIntegrator}) = Strategies.StrategyMetadata() +Strategies.options(integ::MockIntegrator) = Options.StrategyOptions() + +# ============================================================================== +# Mock Integrator Interface Implementation +# ============================================================================== + +function Integrators.build_problem(integ::MockIntegrator, sys::Systems.AbstractSystem, config::Configs.StatePointConfig; variable, cache) + x0 = Configs.initial_state(config) + tspan = Configs.tspan(config) + return MockODEProblem(x0, tspan) +end + +function Integrators.build_problem(integ::MockIntegrator, sys::Systems.AbstractSystem, config::Configs.StateTrajectoryConfig; variable, cache) + x0 = Configs.initial_state(config) + tspan = Configs.tspan(config) + return MockODEProblem(x0, tspan) +end + +function Integrators.build_problem(integ::MockIntegrator, sys::Systems.AbstractSystem, config::Configs.HamiltonianPointConfig; variable, cache) + x0, p0 = Configs.initial_state(config), Configs.initial_costate(config) + tspan = Configs.tspan(config) + return MockODEProblem(vcat(x0, p0), tspan) +end + +function Integrators.build_problem(integ::MockIntegrator, sys::Systems.AbstractSystem, config::Configs.HamiltonianTrajectoryConfig; variable, cache) + x0, p0 = Configs.initial_state(config), Configs.initial_costate(config) + tspan = Configs.tspan(config) + return MockODEProblem(vcat(x0, p0), tspan) +end + +function Integrators.build_options(integ::MockIntegrator, config::Configs.StatePointConfig) + return Dict{Symbol, Any}() +end + +function Integrators.build_options(integ::MockIntegrator, config::Configs.StateTrajectoryConfig) + return Dict{Symbol, Any}() +end + +function Integrators.build_options(integ::MockIntegrator, config::Configs.HamiltonianPointConfig) + return Dict{Symbol, Any}() +end + +function Integrators.build_options(integ::MockIntegrator, config::Configs.HamiltonianTrajectoryConfig) + return Dict{Symbol, Any}() +end + +function Integrators.solve_problem(integ::MockIntegrator, prob::MockODEProblem, opts::Dict{Symbol, Any}; unsafe=Common.__unsafe()) + multiplier = integ.multiplier + t0, tf = prob.tspan + u_final = prob.u0 * multiplier + return MockIntegrationResult(u_final, [t0, tf]) +end + +# ============================================================================== +# Mock Solutions Interface Implementation +# ============================================================================== + +function Integrators.final_state(result::MockIntegrationResult) + return result.u +end + +function Integrators.times(result::MockIntegrationResult) + return result.t +end + +function Integrators.evaluate_at(result::MockIntegrationResult, t::Real) + # Simple linear interpolation + t0, tf = result.t[1], result.t[end] + if t <= t0 + return result.u * 0.5 # Mock initial state + elseif t >= tf + return result.u + else + return result.u * 0.75 # Mock intermediate state + end +end + +function Solutions.build_solution(::Type{Traits.PointTrait}, ::Type{Traits.StateTrait}, config::Configs.AbstractConfig, result::MockIntegrationResult) + # For StatePointConfig, return the final state directly (as expected by _evaluate_phase) + return result.u +end + +function Solutions.build_solution(::Type{Traits.TrajectoryTrait}, ::Type{Traits.StateTrait}, config::Configs.AbstractConfig, result::MockIntegrationResult) + # For StateTrajectoryConfig with StateFlow, return the full result + return result +end + +function Solutions.build_solution(::Type{Traits.PointTrait}, ::Type{Traits.HamiltonianTrait}, config::Configs.AbstractConfig, result::MockIntegrationResult) + # For HamiltonianFlow, return a tuple (x, p) matching _ham_split_solution behavior + x_len = length(result.u) รท 2 + x_part = result.u[1:x_len] + p_part = result.u[x_len+1:end] + return (x_part, p_part) +end + +function Solutions.build_solution(::Type{Traits.TrajectoryTrait}, ::Type{Traits.HamiltonianTrait}, config::Configs.AbstractConfig, result::MockIntegrationResult) + # For HamiltonianTrajectoryConfig, return the full result + return result +end + +# ============================================================================== +# Mock Integrators.merge Implementation +# ============================================================================== + +function Integrators.merge(segments::Vector{<:MockIntegrationResult}) + if isempty(segments) + return MockIntegrationResult(Float64[], Float64[]) + end + + # Combine final states and times + combined_u = vcat([s.u for s in segments]...) + combined_t = vcat([s.t for s in segments]...) + + return MockIntegrationResult(combined_u, combined_t) +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_calling_multiphase() + Test.@testset "Calling Multiphase Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + Test.@testset "_extract_initial_state" begin + x0 = [1.0, 2.0] + p0 = [0.5, 0.3] + + Test.@testset "StatePointConfig" begin + config = Configs.StatePointConfig(0.0, x0, 1.0) + result = MultiPhase._extract_initial_state(config) + Test.@test result === x0 + end + + Test.@testset "StateTrajectoryConfig" begin + config = Configs.StateTrajectoryConfig((0.0, 1.0), x0) + result = MultiPhase._extract_initial_state(config) + Test.@test result === x0 + end + + Test.@testset "HamiltonianPointConfig" begin + config = Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0) + result = MultiPhase._extract_initial_state(config) + Test.@test result === (x0, p0) + end + + Test.@testset "HamiltonianTrajectoryConfig" begin + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), x0, p0) + result = MultiPhase._extract_initial_state(config) + Test.@test result === (x0, p0) + end + end + + Test.@testset "_format_final_output" begin + x = [1.0, 2.0] + p = [0.5, 0.3] + + Test.@testset "MultiPhaseStateFlow" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = FakeIntegrator(:fake_result) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = flow1 * (0.5, flow2) + + result = MultiPhase._format_final_output(mpf, x) + Test.@test result === x + end + + Test.@testset "MultiPhaseHamiltonianFlow" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + hinteg = FakeHamiltonianIntegrator(:fake_result) + hflow1 = Flows.HamiltonianFlow(hsys, hinteg) + hflow2 = Flows.HamiltonianFlow(hsys, hinteg) + hmpf = hflow1 * (0.5, hflow2) + + result = MultiPhase._format_final_output(hmpf, (x, p)) + Test.@test result == vcat(x, p) + end + end + + Test.@testset "_extract_final_state" begin + # Skip complex mocking - test will be covered by integration tests + Test.@testset "skipped - covered by integration tests" begin + Test.@test true + end + end + + Test.@testset "_apply_jump" begin + x = [1.0, 2.0] + p = [0.5, 0.3] + jump = [0.1, 0.2] + + Test.@testset "MultiPhaseStateFlow" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = FakeIntegrator(:fake_result) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = flow1 * (0.5, jump, flow2) # Create mpf with a jump + + result = MultiPhase._apply_jump(mpf, 1, x) + Test.@test result == x + jump + end + + Test.@testset "MultiPhaseHamiltonianFlow" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + hinteg = FakeHamiltonianIntegrator(:fake_result) + hflow1 = Flows.HamiltonianFlow(hsys, hinteg) + hflow2 = Flows.HamiltonianFlow(hsys, hinteg) + hmpf = hflow1 * (0.5, jump, hflow2) + + result = MultiPhase._apply_jump(hmpf, 1, (x, p)) + Test.@test result == (x, p + jump) + end + end + + Test.@testset "_apply_hamiltonian_jump" begin + x = [1.0, 2.0] + p = [0.5, 0.3] + state_tuple = (x, p) + + Test.@testset "jump as Tuple (jump_x, jump_p)" begin + jump_x = [0.1, 0.2] + jump_p = [0.01, 0.02] + result = MultiPhase._apply_hamiltonian_jump(state_tuple, (jump_x, jump_p)) + Test.@test result[1] == x + jump_x + Test.@test result[2] == p + jump_p + end + + Test.@testset "jump as single vector (jump_p only)" begin + jump_p = [0.01, 0.02] + result = MultiPhase._apply_hamiltonian_jump(state_tuple, jump_p) + Test.@test result[1] == x + Test.@test result[2] == p + jump_p + end + end + + Test.@testset "_evaluate_phase" begin + x = [1.0, 2.0] + p = [0.5, 0.3] + + Test.@testset "StateFlow with StatePointConfig" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) # Multiplier of 2 + flow = Flows.StateFlow(sys, integ) + t0 = 0.0 + tf = 1.0 + + result = MultiPhase._evaluate_phase(flow, t0, tf, x, Configs.StatePointConfig(t0, x, tf); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result == x * 2 + end + + Test.@testset "StateFlow with StateTrajectoryConfig" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow = Flows.StateFlow(sys, integ) + tspan = (0.0, 1.0) + + result = MultiPhase._evaluate_phase(flow, 0.0, 1.0, x, Configs.StateTrajectoryConfig(tspan, x); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result.u == x * 2 + end + + Test.@testset "HamiltonianFlow with StatePointConfig" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow = Flows.HamiltonianFlow(hsys, integ) + t0 = 0.0 + tf = 1.0 + + result = MultiPhase._evaluate_phase(flow, t0, tf, (x, p), Configs.HamiltonianPointConfig(t0, x, p, tf); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result[1] == x * 2 + Test.@test result[2] == p * 2 + end + + Test.@testset "HamiltonianFlow with StateTrajectoryConfig" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow = Flows.HamiltonianFlow(hsys, integ) + tspan = (0.0, 1.0) + + result = MultiPhase._evaluate_phase(flow, 0.0, 1.0, (x, p), Configs.HamiltonianTrajectoryConfig(tspan, x, p); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result.u == vcat(x * 2, p * 2) + end + end + + Test.@testset "_evaluate_multiphase with StatePointConfig" begin + x0 = [1.0, 2.0] + p0 = [0.5, 0.3] + + Test.@testset "MultiPhaseStateFlow without jump" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = flow1 * (0.5, flow2) + + result = MultiPhase._evaluate_multiphase(mpf, Configs.StatePointConfig(0.0, x0, 1.0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + # Each phase multiplies by 2, so final is x0 * 2 * 2 = x0 * 4 + Test.@test result == x0 * 4 + end + + Test.@testset "MultiPhaseStateFlow with jump" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + jump = [0.1, 0.2] + mpf = flow1 * (0.5, jump, flow2) + + result = MultiPhase._evaluate_multiphase(mpf, Configs.StatePointConfig(0.0, x0, 1.0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + # Phase 1: x0 * 2, then jump applied, then phase 2: (x0 * 2 + jump) * 2 + expected = (x0 * 2 + jump) * 2 + Test.@test result == expected + end + + Test.@testset "MultiPhaseHamiltonianFlow without jump" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + hflow1 = Flows.HamiltonianFlow(hsys, integ) + hflow2 = Flows.HamiltonianFlow(hsys, integ) + hmpf = hflow1 * (0.5, hflow2) + + result = MultiPhase._evaluate_multiphase(hmpf, Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + # Each phase multiplies by 2, _format_final_output returns vcat(x, p) + Test.@test result == vcat(x0 * 4, p0 * 4) + end + + Test.@testset "MultiPhaseHamiltonianFlow with jump" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + hflow1 = Flows.HamiltonianFlow(hsys, integ) + hflow2 = Flows.HamiltonianFlow(hsys, integ) + jump_p = [0.01, 0.02] + hmpf = hflow1 * (0.5, jump_p, hflow2) + + result = MultiPhase._evaluate_multiphase(hmpf, Configs.HamiltonianPointConfig(0.0, x0, p0, 1.0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + # Phase 1: x0*2, p0*2, then jump_p applied to p, then phase 2: x*2, (p+jump_p)*2 + # _format_final_output returns vcat(x, p) + Test.@test result == vcat(x0 * 4, (p0 * 2 + jump_p) * 2) + end + end + + Test.@testset "_evaluate_multiphase with StateTrajectoryConfig" begin + x0 = [1.0, 2.0] + p0 = [0.5, 0.3] + + Test.@testset "MultiPhaseStateFlow without jump" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = flow1 * (0.5, flow2) + + result = MultiPhase._evaluate_multiphase(mpf, Configs.StateTrajectoryConfig((0.0, 1.0), x0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + # Result should be a merged MockIntegrationResult + Test.@test result isa MockIntegrationResult + # Each phase produces x0*2, so combined should be [x0*2, x0*4] + Test.@test result.u == vcat(x0 * 2, x0 * 4) + end + + Test.@testset "MultiPhaseStateFlow with jump" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + jump = [0.1, 0.2] + mpf = flow1 * (0.5, jump, flow2) + + result = MultiPhase._evaluate_multiphase(mpf, Configs.StateTrajectoryConfig((0.0, 1.0), x0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result isa MockIntegrationResult + # Phase 1: x0*2, then jump, then phase 2: (x0*2+jump)*2 + expected = vcat(x0 * 2, (x0 * 2 + jump) * 2) + Test.@test result.u == expected + end + + Test.@testset "MultiPhaseHamiltonianFlow without jump" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + hflow1 = Flows.HamiltonianFlow(hsys, integ) + hflow2 = Flows.HamiltonianFlow(hsys, integ) + hmpf = hflow1 * (0.5, hflow2) + + result = MultiPhase._evaluate_multiphase(hmpf, Configs.HamiltonianTrajectoryConfig((0.0, 1.0), x0, p0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result isa MockIntegrationResult + # Each phase produces vcat(x0*2, p0*2) + expected = vcat(x0 * 2, p0 * 2, x0 * 4, p0 * 4) + Test.@test result.u == expected + end + + Test.@testset "MultiPhaseHamiltonianFlow with jump" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + hflow1 = Flows.HamiltonianFlow(hsys, integ) + hflow2 = Flows.HamiltonianFlow(hsys, integ) + jump_p = [0.01, 0.02] + hmpf = hflow1 * (0.5, jump_p, hflow2) + + result = MultiPhase._evaluate_multiphase(hmpf, Configs.HamiltonianTrajectoryConfig((0.0, 1.0), x0, p0); variable=Common.__variable(), unsafe=Common.__unsafe()) + + Test.@test result isa MockIntegrationResult + # Phase 1: vcat(x0*2, p0*2), then jump, then phase 2: vcat(x*2, (p+jump_p)*2) + expected = vcat(x0 * 2, p0 * 2, x0 * 4, (p0 * 2 + jump_p) * 2) + Test.@test result.u == expected + end + end + + Test.@testset "MultiPhaseStateFlow callable" begin + x0 = [1.0, 2.0] + + Test.@testset "call with (t0, x0, tf)" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = flow1 * (0.5, flow2) + + result = mpf(0.0, x0, 1.0) + # Each phase multiplies by 2, so final is x0 * 4 + Test.@test result == x0 * 4 + end + + Test.@testset "call with (tspan, x0)" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = flow1 * (0.5, flow2) + + result = mpf((0.0, 1.0), x0) + # Result should be a merged MockIntegrationResult + Test.@test result isa MockIntegrationResult + Test.@test result.u == vcat(x0 * 2, x0 * 4) + end + end + + Test.@testset "MultiPhaseHamiltonianFlow callable" begin + x0 = [1.0, 2.0] + p0 = [0.5, 0.3] + + Test.@testset "call with (t0, x0, p0, tf)" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + hflow1 = Flows.HamiltonianFlow(hsys, integ) + hflow2 = Flows.HamiltonianFlow(hsys, integ) + hmpf = hflow1 * (0.5, hflow2) + + result = hmpf(0.0, x0, p0, 1.0) + # Each phase multiplies by 2 + Test.@test result == vcat(x0 * 4, p0 * 4) + end + + Test.@testset "call with (tspan, x0, p0)" begin + hsys = FakeHamiltonianSystem([1.0, 2.0]) + integ = MockIntegrator(2.0) + hflow1 = Flows.HamiltonianFlow(hsys, integ) + hflow2 = Flows.HamiltonianFlow(hsys, integ) + hmpf = hflow1 * (0.5, hflow2) + + result = hmpf((0.0, 1.0), x0, p0) + # Result should be a merged MockIntegrationResult + Test.@test result isa MockIntegrationResult + Test.@test result.u == vcat(x0 * 2, p0 * 2, x0 * 4, p0 * 4) + end + end + end +end + +end # module + +test_calling_multiphase() = TestCallingMultiphase.test_calling_multiphase() diff --git a/test/suite/multiphase/test_concatenation.jl b/test/suite/multiphase/test_concatenation.jl new file mode 100644 index 00000000..79d465ee --- /dev/null +++ b/test/suite/multiphase/test_concatenation.jl @@ -0,0 +1,518 @@ +module TestConcatenation + +import Test +import CTBase.Exceptions +import CTFlows.MultiPhase +import CTFlows.Systems +import CTFlows.Integrators +import CTFlows.Flows +import CTFlows.Common +import CTFlows.Traits +import CTSolvers.Strategies +import CTSolvers.Options + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types โ€” module top-level (testing-creation.md ยง1) +# ============================================================================== + +struct FakeStateSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + tag::Symbol +end + +Systems.rhs(::FakeStateSystem) = (du, u, p, t) -> (du .= u) + +struct FakeHamiltonianSystem <: Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithoutAD} + tag::Symbol +end + +Systems.rhs(::FakeHamiltonianSystem) = (dz, z, p, t) -> (dz .= z) + +struct FakeIntegrator <: Integrators.AbstractIntegrator end + +Strategies.id(::Type{FakeIntegrator}) = :fake +Strategies.metadata(::Type{FakeIntegrator}) = Strategies.StrategyMetadata() +Strategies.options(::FakeIntegrator) = Options.StrategyOptions() + +# ============================================================================== +# Helpers +# ============================================================================== + +_state_flow(tag=:s) = Flows.StateFlow(FakeStateSystem(tag), FakeIntegrator()) +_ham_flow(tag=:h) = Flows.HamiltonianFlow(FakeHamiltonianSystem(tag), FakeIntegrator()) + +# Build a MultiPhaseStateFlow with n phases (switching times 1.0, 2.0, โ€ฆ) +function _mpstate(n) + f = _state_flow(Symbol(:s, 1)) + for i in 2:n + f = f * (Float64(i - 1), _state_flow(Symbol(:s, i))) + end + return f +end + +# Build a MultiPhaseHamiltonianFlow with n phases +function _mpham(n) + f = _ham_flow(Symbol(:h, 1)) + for i in 2:n + f = f * (Float64(i - 1), _ham_flow(Symbol(:h, i))) + end + return f +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_concatenation() + Test.@testset "Concatenation Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ====================================================================== + # 1. Helpers on AbstractFlow (single-phase dispatch) + # ====================================================================== + Test.@testset "Helpers โ€” single-phase (AbstractFlow)" begin + sf = _state_flow() + hf = _ham_flow() + + Test.@testset "get_flows" begin + Test.@test MultiPhase.get_flows(sf) == [sf] + Test.@test length(MultiPhase.get_flows(sf)) == 1 + Test.@test MultiPhase.get_flows(hf) == [hf] + Test.@test length(MultiPhase.get_flows(hf)) == 1 + # type stability + Test.@inferred MultiPhase.get_flows(sf) + Test.@inferred MultiPhase.get_flows(hf) + end + + Test.@testset "get_switching_times" begin + Test.@test MultiPhase.get_switching_times(sf) == Real[] + Test.@test isempty(MultiPhase.get_switching_times(sf)) + Test.@test MultiPhase.get_switching_times(hf) == Real[] + Test.@inferred MultiPhase.get_switching_times(sf) + Test.@inferred MultiPhase.get_switching_times(hf) + end + + Test.@testset "get_jumps" begin + Test.@test MultiPhase.get_jumps(sf) == Any[] + Test.@test isempty(MultiPhase.get_jumps(sf)) + Test.@test MultiPhase.get_jumps(hf) == Any[] + Test.@inferred MultiPhase.get_jumps(sf) + Test.@inferred MultiPhase.get_jumps(hf) + end + end + + # ====================================================================== + # 2. Helpers on AnyMultiPhaseFlow (multi-phase dispatch) + # ====================================================================== + Test.@testset "Helpers โ€” multi-phase (AnyMultiPhaseFlow)" begin + mpstate = _mpstate(3) # 3 phases, switches [1.0, 2.0], jumps [nothing, nothing] + mpham = _mpham(3) + + Test.@testset "get_flows" begin + Test.@test length(MultiPhase.get_flows(mpstate)) == 3 + Test.@test MultiPhase.get_flows(mpstate) === mpstate.flows + Test.@test length(MultiPhase.get_flows(mpham)) == 3 + Test.@test MultiPhase.get_flows(mpham) === mpham.flows + Test.@inferred MultiPhase.get_flows(mpstate) + Test.@inferred MultiPhase.get_flows(mpham) + end + + Test.@testset "get_switching_times" begin + Test.@test MultiPhase.get_switching_times(mpstate) == [1.0, 2.0] + Test.@test MultiPhase.get_switching_times(mpstate) === mpstate.switching_times + Test.@inferred MultiPhase.get_switching_times(mpstate) + end + + Test.@testset "get_jumps" begin + Test.@test MultiPhase.get_jumps(mpstate) == [nothing, nothing] + Test.@test MultiPhase.get_jumps(mpstate) === mpstate.jumps + Test.@inferred MultiPhase.get_jumps(mpstate) + end + end + + # ====================================================================== + # 3. StateFlow concatenation โ€” all combinations + # ====================================================================== + Test.@testset "StateFlow concatenation" begin + jump = x -> 2 .* x + + # ------------------------------------------------------------------ + # 3a. 1 ร— 1 + # ------------------------------------------------------------------ + Test.@testset "1 ร— 1 โ€” no jump" begin + f1, f2 = _state_flow(:a), _state_flow(:b) + mpf = f1 * (0.5, f2) + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + Test.@test length(mpf.flows) == 2 + Test.@test mpf.switching_times == [0.5] + Test.@test mpf.jumps == [nothing] + Test.@inferred f1 * (0.5, f2) + end + + Test.@testset "1 ร— 1 โ€” with jump" begin + f1, f2 = _state_flow(:a), _state_flow(:b) + mpf = f1 * (0.5, jump, f2) + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + Test.@test length(mpf.flows) == 2 + Test.@test mpf.switching_times == [0.5] + Test.@test mpf.jumps == [jump] + Test.@inferred f1 * (0.5, jump, f2) + end + + # ------------------------------------------------------------------ + # 3b. n ร— 1 (regression: existing MultiPhase ร— SinglePhase) + # ------------------------------------------------------------------ + Test.@testset "n ร— 1 โ€” no jump" begin + mpf1 = _mpstate(2) # phases: [s1, s2], switches: [1.0], jumps: [nothing] + f3 = _state_flow(:s3) + mpf = mpf1 * (2.0, f3) + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + Test.@test length(mpf.flows) == 3 + Test.@test mpf.switching_times == [1.0, 2.0] + Test.@test mpf.jumps == [nothing, nothing] + end + + Test.@testset "n ร— 1 โ€” with jump" begin + mpf1 = _mpstate(2) + f3 = _state_flow(:s3) + mpf = mpf1 * (2.0, jump, f3) + Test.@test length(mpf.flows) == 3 + Test.@test mpf.switching_times == [1.0, 2.0] + Test.@test mpf.jumps[1] === nothing + Test.@test mpf.jumps[2] === jump + end + + # ------------------------------------------------------------------ + # 3c. 1 ร— n (new case) + # ------------------------------------------------------------------ + Test.@testset "1 ร— n โ€” no jump" begin + f1 = _state_flow(:s0) + mpf2 = _mpstate(2) # phases: [s1, s2], switches: [1.0], jumps: [nothing] + mpf = f1 * (0.5, mpf2) + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + Test.@test length(mpf.flows) == 3 + Test.@test mpf.switching_times == [0.5, 1.0] + Test.@test mpf.jumps == [nothing, nothing] + end + + Test.@testset "1 ร— n โ€” with jump" begin + f1 = _state_flow(:s0) + mpf2 = _mpstate(2) + mpf = f1 * (0.5, jump, mpf2) + Test.@test length(mpf.flows) == 3 + Test.@test mpf.switching_times == [0.5, 1.0] + Test.@test mpf.jumps[1] === jump + Test.@test mpf.jumps[2] === nothing + end + + # ------------------------------------------------------------------ + # 3d. n ร— m (new case) + # ------------------------------------------------------------------ + Test.@testset "n ร— m โ€” no jump" begin + f1 = _state_flow(:s1) + f2 = _state_flow(:s2) + f3 = _state_flow(:s3) + f4 = _state_flow(:s4) + f5 = _state_flow(:s5) + mpf1 = f1 * (1.0, f2) * (2.0, f3) # switches: [1.0, 2.0] + mpf2 = f4 * (5.0, f5) # switches: [5.0] + mpf = mpf1 * (3.0, mpf2) # switches: [1.0, 2.0, 3.0, 5.0] + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + Test.@test length(mpf.flows) == 5 + Test.@test mpf.switching_times == [1.0, 2.0, 3.0, 5.0] + Test.@test all(==(nothing), mpf.jumps) + Test.@test length(mpf.jumps) == 4 + end + + Test.@testset "n ร— m โ€” with jump" begin + f1 = _state_flow(:s1) + f2 = _state_flow(:s2) + f3 = _state_flow(:s3) + f4 = _state_flow(:s4) + f5 = _state_flow(:s5) + mpf1 = f1 * (1.0, f2) * (2.0, f3) # switches: [1.0, 2.0], jumps: [nothing, nothing] + mpf2 = f4 * (5.0, f5) # switches: [5.0], jumps: [nothing] + mpf = mpf1 * (3.0, jump, mpf2) # switches: [1.0, 2.0, 3.0, 5.0], jumps: [nothing, nothing, jump, nothing] + Test.@test length(mpf.flows) == 5 + Test.@test mpf.switching_times == [1.0, 2.0, 3.0, 5.0] + Test.@test mpf.jumps[1] === nothing # from mpf1 + Test.@test mpf.jumps[2] === nothing # from mpf1 + Test.@test mpf.jumps[3] === jump # new junction + Test.@test mpf.jumps[4] === nothing # from mpf2 + end + + # ------------------------------------------------------------------ + # 3e. Merge invariant: explicit formula check + # ------------------------------------------------------------------ + Test.@testset "Merge invariant" begin + f1 = _state_flow(:a) + mpf2 = _mpstate(2) # switches: [1.0], jumps: [nothing] + t_s = 0.5 + mpf = f1 * (t_s, mpf2) + + expected_flows = [MultiPhase.get_flows(f1)..., MultiPhase.get_flows(mpf2)...] + expected_times = [MultiPhase.get_switching_times(f1)..., t_s, MultiPhase.get_switching_times(mpf2)...] + expected_jumps = [MultiPhase.get_jumps(f1)..., nothing, MultiPhase.get_jumps(mpf2)...] + + Test.@test length(mpf.flows) == length(expected_flows) + Test.@test mpf.switching_times == expected_times + Test.@test mpf.jumps == expected_jumps + end + end + + # ====================================================================== + # 4. HamiltonianFlow concatenation โ€” all combinations + # ====================================================================== + Test.@testset "HamiltonianFlow concatenation" begin + jump = z -> 2 .* z + jump_x = x -> x .+ 1 + jump_p = p -> p .* 2 + + # ------------------------------------------------------------------ + # 4a. 1 ร— 1 + # ------------------------------------------------------------------ + Test.@testset "1 ร— 1 โ€” no jump" begin + f1, f2 = _ham_flow(:a), _ham_flow(:b) + mpf = f1 * (0.5, f2) + Test.@test mpf isa MultiPhase.MultiPhaseHamiltonianFlow + Test.@test length(mpf.flows) == 2 + Test.@test mpf.switching_times == [0.5] + Test.@test mpf.jumps == [nothing] + Test.@inferred f1 * (0.5, f2) + end + + Test.@testset "1 ร— 1 โ€” with jump" begin + f1, f2 = _ham_flow(:a), _ham_flow(:b) + mpf = f1 * (0.5, jump, f2) + Test.@test mpf isa MultiPhase.MultiPhaseHamiltonianFlow + Test.@test mpf.jumps == [jump] + Test.@inferred f1 * (0.5, jump, f2) + end + + Test.@testset "1 ร— 1 โ€” with (jump_x, jump_p)" begin + f1, f2 = _ham_flow(:a), _ham_flow(:b) + mpf = f1 * (0.5, jump_x, jump_p, f2) + Test.@test mpf isa MultiPhase.MultiPhaseHamiltonianFlow + Test.@test length(mpf.flows) == 2 + Test.@test mpf.switching_times == [0.5] + Test.@test mpf.jumps == [(jump_x, jump_p)] + j = mpf.jumps[1] + Test.@test j isa Tuple + Test.@test j[1] === jump_x + Test.@test j[2] === jump_p + Test.@inferred f1 * (0.5, jump_x, jump_p, f2) + end + + # ------------------------------------------------------------------ + # 4b. n ร— 1 + # ------------------------------------------------------------------ + Test.@testset "n ร— 1 โ€” no jump" begin + mpf1 = _mpham(2) + f3 = _ham_flow(:h3) + mpf = mpf1 * (2.0, f3) + Test.@test length(mpf.flows) == 3 + Test.@test mpf.switching_times == [1.0, 2.0] + Test.@test mpf.jumps == [nothing, nothing] + end + + Test.@testset "n ร— 1 โ€” with jump" begin + mpf1 = _mpham(2) + f3 = _ham_flow(:h3) + mpf = mpf1 * (2.0, jump, f3) + Test.@test length(mpf.flows) == 3 + Test.@test mpf.jumps[1] === nothing + Test.@test mpf.jumps[2] === jump + end + + # ------------------------------------------------------------------ + # 4c. 1 ร— n + # ------------------------------------------------------------------ + Test.@testset "1 ร— n โ€” no jump" begin + f1 = _ham_flow(:h0) + mpf2 = _mpham(2) + mpf = f1 * (0.5, mpf2) + Test.@test mpf isa MultiPhase.MultiPhaseHamiltonianFlow + Test.@test length(mpf.flows) == 3 + Test.@test mpf.switching_times == [0.5, 1.0] + Test.@test mpf.jumps == [nothing, nothing] + end + + Test.@testset "1 ร— n โ€” with jump" begin + f1 = _ham_flow(:h0) + mpf2 = _mpham(2) + mpf = f1 * (0.5, jump, mpf2) + Test.@test length(mpf.flows) == 3 + Test.@test mpf.jumps[1] === jump + Test.@test mpf.jumps[2] === nothing + end + + Test.@testset "1 ร— n โ€” with (jump_x, jump_p)" begin + f1 = _ham_flow(:h0) + mpf2 = _mpham(2) + mpf = f1 * (0.5, jump_x, jump_p, mpf2) + Test.@test length(mpf.flows) == 3 + Test.@test mpf.jumps[1] == (jump_x, jump_p) + Test.@test mpf.jumps[2] === nothing + end + + # ------------------------------------------------------------------ + # 4d. n ร— m + # ------------------------------------------------------------------ + Test.@testset "n ร— m โ€” no jump" begin + f1 = _ham_flow(:h1) + f2 = _ham_flow(:h2) + f3 = _ham_flow(:h3) + f4 = _ham_flow(:h4) + f5 = _ham_flow(:h5) + mpf1 = f1 * (1.0, f2) * (2.0, f3) # switches: [1.0, 2.0] + mpf2 = f4 * (5.0, f5) # switches: [5.0] + mpf = mpf1 * (3.0, mpf2) # switches: [1.0, 2.0, 3.0, 5.0] + Test.@test mpf isa MultiPhase.MultiPhaseHamiltonianFlow + Test.@test length(mpf.flows) == 5 + Test.@test mpf.switching_times == [1.0, 2.0, 3.0, 5.0] + Test.@test all(==(nothing), mpf.jumps) + end + + Test.@testset "n ร— m โ€” with jump" begin + f1 = _ham_flow(:h1) + f2 = _ham_flow(:h2) + f3 = _ham_flow(:h3) + f4 = _ham_flow(:h4) + f5 = _ham_flow(:h5) + mpf1 = f1 * (1.0, f2) * (2.0, f3) # switches: [1.0, 2.0], jumps: [nothing, nothing] + mpf2 = f4 * (5.0, f5) # switches: [5.0], jumps: [nothing] + mpf = mpf1 * (3.0, jump, mpf2) # switches: [1.0, 2.0, 3.0, 5.0], jumps: [nothing, nothing, jump, nothing] + Test.@test length(mpf.flows) == 5 + Test.@test mpf.jumps[1] === nothing + Test.@test mpf.jumps[2] === nothing + Test.@test mpf.jumps[3] === jump + Test.@test mpf.jumps[4] === nothing + end + + Test.@testset "n ร— m โ€” with (jump_x, jump_p)" begin + f1 = _ham_flow(:h1) + f2 = _ham_flow(:h2) + f3 = _ham_flow(:h3) + f4 = _ham_flow(:h4) + f5 = _ham_flow(:h5) + mpf1 = f1 * (1.0, f2) * (2.0, f3) # switches: [1.0, 2.0], jumps: [nothing, nothing] + mpf2 = f4 * (5.0, f5) # switches: [5.0], jumps: [nothing] + mpf = mpf1 * (3.0, jump_x, jump_p, mpf2) # switches: [1.0, 2.0, 3.0, 5.0], jumps: [nothing, nothing, (jump_x, jump_p), nothing] + Test.@test mpf.jumps[3] == (jump_x, jump_p) + end + + # ------------------------------------------------------------------ + # 4e. Merge invariant + # ------------------------------------------------------------------ + Test.@testset "Merge invariant" begin + f1 = _ham_flow(:h0) + mpf2 = _mpham(2) + t_s = 0.5 + mpf = f1 * (t_s, mpf2) + + expected_flows = [MultiPhase.get_flows(f1)..., MultiPhase.get_flows(mpf2)...] + expected_times = [MultiPhase.get_switching_times(f1)..., t_s, MultiPhase.get_switching_times(mpf2)...] + expected_jumps = [MultiPhase.get_jumps(f1)..., nothing, MultiPhase.get_jumps(mpf2)...] + + Test.@test length(mpf.flows) == length(expected_flows) + Test.@test mpf.switching_times == expected_times + Test.@test mpf.jumps == expected_jumps + end + end + + # ====================================================================== + # 5. Long chains (3+ operands via chaining) + # ====================================================================== + Test.@testset "Long chains" begin + Test.@testset "StateFlow โ€” 4 phases chained" begin + f1, f2, f3, f4 = (_state_flow(Symbol(:s, i)) for i in 1:4) + mpf = f1 * (1.0, f2) * (2.0, f3) * (3.0, f4) + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + Test.@test length(mpf.flows) == 4 + Test.@test mpf.switching_times == [1.0, 2.0, 3.0] + Test.@test length(mpf.jumps) == 3 + end + + Test.@testset "HamiltonianFlow โ€” 4 phases chained" begin + f1, f2, f3, f4 = (_ham_flow(Symbol(:h, i)) for i in 1:4) + mpf = f1 * (1.0, f2) * (2.0, f3) * (3.0, f4) + Test.@test mpf isa MultiPhase.MultiPhaseHamiltonianFlow + Test.@test length(mpf.flows) == 4 + Test.@test mpf.switching_times == [1.0, 2.0, 3.0] + end + + Test.@testset "Mixed single/multi โ€” 5 phases" begin + f1 = _state_flow(:s1) + f2 = _state_flow(:s2) + f3 = _state_flow(:s3) + f4 = _state_flow(:s4) + f5 = _state_flow(:s5) + mpf2 = f2 * (2.0, f3) # switches: [2.0] + mpf3 = f4 * (5.0, f5) # switches: [5.0] + mpf = f1 * (1.0, mpf2) * (3.0, mpf3) # switches: [1.0, 2.0, 3.0, 5.0] + Test.@test length(mpf.flows) == 5 + Test.@test mpf.switching_times == [1.0, 2.0, 3.0, 5.0] + Test.@test length(mpf.jumps) == 4 + end + end + + # ====================================================================== + # 6. Switching times validation + # ====================================================================== + Test.@testset "Switching times validation" begin + Test.@testset "Valid switching times โ€” no error" begin + f1, f2 = _state_flow(:a), _state_flow(:b) + mpf = f1 * (1.0, f2) + Test.@test mpf isa MultiPhase.MultiPhaseStateFlow + end + + Test.@testset "Invalid switching times โ€” t_switch not greater than f1 times" begin + mpf1 = _mpstate(2) # switches: [1.0] + f3 = _state_flow(:s3) + Test.@test_throws Exceptions.PreconditionError mpf1 * (0.5, f3) # 0.5 < 1.0 + end + + Test.@testset "Invalid switching times โ€” t_switch not less than f2 times" begin + f1 = _state_flow(:s0) + mpf2 = _mpstate(2) # switches: [1.0] + Test.@test_throws Exceptions.PreconditionError f1 * (2.0, mpf2) # 2.0 > 1.0 + end + + Test.@testset "Invalid switching times โ€” HamiltonianFlow" begin + mpf1 = _mpham(2) # switches: [1.0] + f3 = _ham_flow(:h3) + Test.@test_throws Exceptions.PreconditionError mpf1 * (0.5, f3) + end + + Test.@testset "PreconditionError message quality" begin + mpf1 = _mpstate(2) # switches: [1.0] + f3 = _state_flow(:s3) + try + mpf1 * (0.5, f3) + Test.@test false # Should have thrown + catch e + Test.@test e isa Exceptions.PreconditionError + Test.@test occursin("strictly increasing", e.msg) + Test.@test occursin("flow concatenation", e.context) + end + end + end + + # ====================================================================== + # 7. Exports + # ====================================================================== + Test.@testset "Exports" begin + for sym in (:MultiPhaseStateFlow, :MultiPhaseHamiltonianFlow, + :n_phases, :get_flow, :get_switching_time, :get_jump, + :get_flows, :get_switching_times, :get_jumps) + Test.@test sym in names(MultiPhase) + end + end + + end # top-level testset +end + +end # module + +test_concatenation() = TestConcatenation.test_concatenation() \ No newline at end of file diff --git a/test/suite/multiphase/test_concatenation_sciml.jl b/test/suite/multiphase/test_concatenation_sciml.jl new file mode 100644 index 00000000..d85b308a --- /dev/null +++ b/test/suite/multiphase/test_concatenation_sciml.jl @@ -0,0 +1,251 @@ +module TestConcatenationSciML + +import Test +import CTFlows.MultiPhase +import CTFlows.Systems +import CTFlows.Integrators +import CTFlows.Flows +import CTFlows.Data +import CTFlows.Common +import CTFlows.Solutions + +using OrdinaryDiffEqTsit5 +using Plots +using SciMLBase: SciMLBase, ODEFunction + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_concatenation_sciml() + Test.@testset "Concatenation with SciML Integration" verbose=VERBOSE showtiming=SHOWTIMING begin + + # Linear system: dx/dt = -x, solution: x(t) = x0 * exp(-t) + linear_oop(u) = -u + + Test.@testset "Two-phase linear system" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + + mpf = flow1 * (0.5, flow2) + + t0 = 0.0; tf = 1.0; x0 = [1.0] + xf = mpf(t0, x0, tf) + + Test.@test xf[1] โ‰ˆ exp(-1.0) atol = 1e-3 + end + + Test.@testset "Jump at switching time" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + jump = 10.0 + + mpf = f * (0.5, jump, f) + + x0 = [1.0] + xf = mpf(0.0, x0, 1.0) + + expected = (exp(-0.5) + 10.0) * exp(-0.5) + + Test.@test xf[1] โ‰ˆ expected atol = 1e-3 + end + + Test.@testset "Three-phase consistency" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + + mpf = f * (0.3, f) * (0.7, f) + + x0 = [2.0] + xf = mpf(0.0, x0, 1.0) + + Test.@test xf[1] โ‰ˆ 2.0 * exp(-1.0) atol = 1e-4 + end + + Test.@testset "Trajectory merging" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + mpf = f * (0.5, f) + + sol = mpf((0.0, 1.0), [1.0]) + + # sol is VectorFieldSolution, use accessors + xf = Integrators.final_state(sol) + Test.@test xf[1] โ‰ˆ exp(-1.0) atol = 1e-4 + + ts = Integrators.times(sol) + for t in ts + u = sol(t) # Evaluate at time t + Test.@test u[1] โ‰ˆ exp(-t) atol = 1e-3 + end + end + + Test.@testset "Trajectory with jump discontinuity" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + mpf = f * (0.5, 5.0, f) + + sol = mpf((0.0, 1.0), [1.0]) + + # Verify discontinuity at switching time + t_switch = 0.5 + u_before = sol(t_switch - 1e-6) + u_after = sol(t_switch + 1e-6) + + # After jump, state should be increased by 5.0 + expected_before = exp(-0.5) + expected_after = expected_before + 5.0 + + Test.@test u_before[1] โ‰ˆ expected_before atol = 1e-3 + Test.@test u_after[1] โ‰ˆ expected_after atol = 1e-3 + end + + Test.@testset "Multiple jumps" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + mpf = f * (0.3, 2.0, f) * (0.6, 3.0, f) + + x0 = [1.0] + xf = mpf(0.0, x0, 1.0) + + # Expected: x0*exp(-0.3) + 2, then (x0*exp(-0.3)+2)*exp(-0.3) + 3, then *exp(-0.4) + expected = ((1.0 * exp(-0.3) + 2.0) * exp(-0.3) + 3.0) * exp(-0.4) + + Test.@test xf[1] โ‰ˆ expected atol = 1e-3 + end + + Test.@testset "Jump is 0" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + mpf = f * (0.5, 0.0, f) + + x0 = [1.0] + xf = mpf(0.0, x0, 1.0) + + # With zero jump, should be same as no jump + Test.@test xf[1] โ‰ˆ exp(-1.0) atol = 1e-4 + end + + Test.@testset "Switch after final time" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + # Switch at 2.0, but final time is 1.0 - switch should be ignored + mpf = f * (2.0, f) + + x0 = [1.0] + xf = mpf(0.0, x0, 1.0) + + # Should behave like single flow since switch is after final time + Test.@test xf[1] โ‰ˆ exp(-1.0) atol = 1e-4 + end + + Test.@testset "Equivalence with SciML callback" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + jump_value = 5.0 + mpf = f * (0.5, jump_value, f) + + x0 = [1.0] + xf_mpf = mpf(0.0, x0, 1.0) + + # Expected value with jump + expected = (exp(-0.5) + jump_value) * exp(-0.5) + Test.@test xf_mpf[1] โ‰ˆ expected atol = 1e-3 + end + + Test.@testset "StateTrajectoryConfig and plot" begin + vf = Data.VectorField(linear_oop) + sys = Systems.VectorFieldSystem(vf) + integ = Integrators.SciML() + + f = Flows.StateFlow(sys, integ) + mpf = f * (0.5, f) + + # Call with tspan (StateTrajectoryConfig) + sol = mpf((0.0, 1.0), [1.0]) + + # Verify it's a VectorFieldSolution + Test.@test sol isa Solutions.VectorFieldSolution + + # Test plot function + p = plot(sol; label="x(t)", title="Trajectory") + Test.@test p isa Plots.Plot + end + + # ==================================================================== + # INTEGRATION TESTS - SciMLFunctionSystem Multi-phase + # ==================================================================== + + Test.@testset "SciMLFunctionSystem โ€” Two-phase iip ODEFunction" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f) + mpf = flow * (0.5, flow) + xf = mpf(0.0, [1.0], 1.0; variable=1.0) + Test.@test xf[1] โ‰ˆ exp(-1.0) atol=1e-3 + end + + Test.@testset "SciMLFunctionSystem โ€” Two-phase oop ODEFunction" begin + f = ODEFunction{false}((u, p, t) -> -p .* u) + flow = Flows.Flow(f) + mpf = flow * (0.5, flow) + xf = mpf(0.0, [1.0], 1.0; variable=1.0) + Test.@test xf[1] โ‰ˆ exp(-1.0) atol=1e-3 + end + + Test.@testset "SciMLFunctionSystem โ€” Jump at switching time" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f) + jump = 10.0 + mpf = flow * (0.5, jump, flow) + x0 = [1.0] + xf = mpf(0.0, x0, 1.0; variable=1.0) + expected = (exp(-0.5) + 10.0) * exp(-0.5) + Test.@test xf[1] โ‰ˆ expected atol=1e-3 + end + + Test.@testset "SciMLFunctionSystem โ€” Trajectory merging" begin + f = ODEFunction((du, u, p, t) -> du .= -p .* u) + flow = Flows.Flow(f) + mpf = flow * (0.5, flow) + sol = mpf((0.0, 1.0), [1.0]; variable=1.0) + Test.@test sol isa Solutions.VectorFieldSolution + xf = Integrators.final_state(sol) + Test.@test xf[1] โ‰ˆ exp(-1.0) atol=1e-4 + end + + end +end + +end # module + +test_concatenation_sciml() = TestConcatenationSciML.test_concatenation_sciml() \ No newline at end of file diff --git a/test/suite/multiphase/test_multiphase_flow.jl b/test/suite/multiphase/test_multiphase_flow.jl new file mode 100644 index 00000000..d33c8af3 --- /dev/null +++ b/test/suite/multiphase/test_multiphase_flow.jl @@ -0,0 +1,183 @@ +module TestMultiPhaseFlow + +import Test +import CTFlows.MultiPhase +import CTFlows.Systems +import CTFlows.Integrators +import CTFlows.Flows +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing +# ============================================================================== + +struct FakeStateSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +function Systems.rhs(sys::FakeStateSystem) + return (du, u, p, t) -> du .= sys.data .* u +end + +struct FakeIntegrator <: Integrators.AbstractIntegrator + result::Any +end + +import CTSolvers.Strategies +import CTSolvers.Options + +Strategies.id(::Type{FakeIntegrator}) = :fake_integrator +Strategies.metadata(::Type{FakeIntegrator}) = Strategies.StrategyMetadata() +Strategies.options(integ::FakeIntegrator) = Options.StrategyOptions() + +# ============================================================================== +# Test function +# ============================================================================== + +function test_multiphase_flow() + Test.@testset "MultiPhaseFlow Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + Test.@testset "MultiPhaseStateFlow" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = FakeIntegrator(:fake_result) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpsf = MultiPhase.MultiPhaseStateFlow([flow1, flow2], [0.5], [nothing]) + + Test.@testset "stores flows" begin + Test.@test length(mpsf.flows) == 2 + Test.@test mpsf.flows[1] === flow1 + Test.@test mpsf.flows[2] === flow2 + end + + Test.@testset "stores switching times" begin + Test.@test mpsf.switching_times == [0.5] + end + + Test.@testset "stores jumps" begin + Test.@test mpsf.jumps == [nothing] + end + + Test.@testset "system returns Vector of systems" begin + sys_result = Flows.system(mpsf) + Test.@test sys_result isa Vector + Test.@test eltype(sys_result) <: Systems.AbstractSystem + Test.@test length(sys_result) == 2 + end + + Test.@testset "Display Methods" begin + Test.@testset "tree-style display works" begin + io = IOBuffer() + show(io, MIME("text/plain"), mpsf) + output = String(take!(io)) + Test.@test occursin("MultiPhaseStateFlow", output) + Test.@test occursin("phases: 2", output) + Test.@test occursin("systems: FakeStateSystem", output) + Test.@test occursin("integrators: FakeIntegrator", output) + Test.@test occursin("switching_times: [0.5]", output) + end + + Test.@testset "compact display works" begin + io = IOBuffer() + show(io, mpsf) + output = String(take!(io)) + Test.@test occursin("MultiPhaseStateFlow(phases=2", output) + Test.@test occursin("switching_times=[0.5])", output) + end + end + + Test.@testset "Getter Methods" begin + Test.@testset "n_phases returns number of phases" begin + Test.@test MultiPhase.n_phases(mpsf) == 2 + end + + Test.@testset "get_flow returns correct flow" begin + Test.@test MultiPhase.get_flow(mpsf, 1) === flow1 + Test.@test MultiPhase.get_flow(mpsf, 2) === flow2 + end + + Test.@testset "get_switching_time returns correct time" begin + Test.@test MultiPhase.get_switching_time(mpsf, 1) == 0.5 + end + + Test.@testset "get_jump returns correct jump" begin + Test.@test MultiPhase.get_jump(mpsf, 1) === nothing + end + end + end + + Test.@testset "MultiPhaseHamiltonianFlow" begin + # TODO: Add HamiltonianSystem fake when implemented + Test.@testset "type exists" begin + Test.@test isdefined(MultiPhase, :MultiPhaseHamiltonianFlow) + end + end + + Test.@testset "Helper Methods โ€” AbstractFlow (single-phase)" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = FakeIntegrator(:fake_result) + flow = Flows.StateFlow(sys, integ) + + Test.@testset "get_flows returns single-element vector" begin + result = MultiPhase.get_flows(flow) + Test.@test result isa Vector + Test.@test length(result) == 1 + Test.@test result[1] === flow + Test.@test Test.@inferred(MultiPhase.get_flows(flow)) isa Vector + end + + Test.@testset "get_switching_times returns empty Real vector" begin + result = MultiPhase.get_switching_times(flow) + Test.@test result isa Vector{Real} + Test.@test length(result) == 0 + Test.@test Test.@inferred(MultiPhase.get_switching_times(flow)) isa Vector{Real} + end + + Test.@testset "get_jumps returns empty Any vector" begin + result = MultiPhase.get_jumps(flow) + Test.@test result isa Vector{Any} + Test.@test length(result) == 0 + Test.@test Test.@inferred(MultiPhase.get_jumps(flow)) isa Vector{Any} + end + end + + Test.@testset "Helper Methods โ€” AnyMultiPhaseFlow (multi-phase)" begin + sys = FakeStateSystem([1.0, 2.0]) + integ = FakeIntegrator(:fake_result) + flow1 = Flows.StateFlow(sys, integ) + flow2 = Flows.StateFlow(sys, integ) + mpf = MultiPhase.MultiPhaseStateFlow([flow1, flow2], [0.5], [nothing]) + + Test.@testset "get_flows delegates to mpf.flows" begin + result = MultiPhase.get_flows(mpf) + Test.@test result === mpf.flows + end + + Test.@testset "get_switching_times delegates to mpf.switching_times" begin + result = MultiPhase.get_switching_times(mpf) + Test.@test result === mpf.switching_times + end + + Test.@testset "get_jumps delegates to mpf.jumps" begin + result = MultiPhase.get_jumps(mpf) + Test.@test result === mpf.jumps + end + end + + Test.@testset "Exports" begin + Test.@testset "helper methods are exported" begin + Test.@test isdefined(MultiPhase, :get_flows) + Test.@test isdefined(MultiPhase, :get_switching_times) + Test.@test isdefined(MultiPhase, :get_jumps) + end + end + end +end + +end # module + +test_multiphase_flow() = TestMultiPhaseFlow.test_multiphase_flow() diff --git a/test/suite/multiphase/test_multiphase_module.jl b/test/suite/multiphase/test_multiphase_module.jl new file mode 100644 index 00000000..b1375838 --- /dev/null +++ b/test/suite/multiphase/test_multiphase_module.jl @@ -0,0 +1,26 @@ +module TestMultiPhaseModule + +import Test +import CTFlows.MultiPhase + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_multiphase_module() + Test.@testset "MultiPhase Module Exports" verbose=VERBOSE showtiming=SHOWTIMING begin + + Test.@testset "Types are exported" begin + Test.@testset "MultiPhaseStateFlow" begin + Test.@test isdefined(MultiPhase, :MultiPhaseStateFlow) + end + + Test.@testset "MultiPhaseHamiltonianFlow" begin + Test.@test isdefined(MultiPhase, :MultiPhaseHamiltonianFlow) + end + end + end +end + +end # module + +test_multiphase_module() = TestMultiPhaseModule.test_multiphase_module() diff --git a/test/suite/solutions/test_building_solutions.jl b/test/suite/solutions/test_building_solutions.jl new file mode 100644 index 00000000..38e457ea --- /dev/null +++ b/test/suite/solutions/test_building_solutions.jl @@ -0,0 +1,174 @@ +module TestBuildingSolutions + +import Test +import CTFlows.Solutions +import CTFlows.Systems +import CTFlows.Common +import CTFlows.Configs +import CTFlows.Data +import CTFlows.Integrators + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing +# ============================================================================== + +""" +Fake integration result for testing build_solution. +""" +struct FakeIntegrationResult <: Integrators.AbstractIntegrationResult + u::Vector{Vector{Float64}} +end + +Integrators.final_state(r::FakeIntegrationResult) = r.u[end] + +# ============================================================================== +# Test function +# ============================================================================== + +function test_building_solutions() + Test.@testset "Building Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - build_solution for StatePointConfig + # ==================================================================== + + Test.@testset "build_solution - StatePointConfig" begin + Test.@testset "vector initial condition returns final state" begin + sys = Systems.VectorFieldSystem(Data.VectorField(x -> -x; is_autonomous=true, is_variable=false)) + result = FakeIntegrationResult([[1.0, 2.0], [0.5, 1.0]]) + config = Configs.StatePointConfig(0.0, [1.0, 2.0], 1.0) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output == [0.5, 1.0] + Test.@test typeof(config) <: Configs.StatePointConfig{Float64, <:AbstractVector, Float64} + end + + Test.@testset "scalar initial condition unwraps length-1 vector" begin + sys = Systems.VectorFieldSystem(Data.VectorField(x -> -x; is_autonomous=true, is_variable=false)) + result = FakeIntegrationResult([[3.0], [1.5]]) + config = Configs.StatePointConfig(0.0, 3.0, 1.0) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output == 1.5 + Test.@test typeof(config) == Configs.StatePointConfig{Float64, Float64, Float64} + end + end + + # ==================================================================== + # UNIT TESTS - build_solution for StateTrajectoryConfig + # ==================================================================== + + Test.@testset "build_solution - StateTrajectoryConfig" begin + Test.@testset "returns VectorFieldSolution wrapping result" begin + sys = Systems.VectorFieldSystem(Data.VectorField(x -> -x; is_autonomous=true, is_variable=false)) + result = FakeIntegrationResult([[1.0, 2.0], [0.5, 1.0]]) + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 2.0]) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output isa Solutions.VectorFieldSolution + end + + Test.@testset "VectorFieldSolution contains correct result" begin + sys = Systems.VectorFieldSystem(Data.VectorField(x -> -x; is_autonomous=true, is_variable=false)) + result = FakeIntegrationResult([[1.0, 2.0], [0.5, 1.0]]) + config = Configs.StateTrajectoryConfig((0.0, 1.0), [1.0, 2.0]) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output.result === result + end + end + + # ==================================================================== + # UNIT TESTS - build_solution for HamiltonianPointConfig + # ==================================================================== + + Test.@testset "build_solution - HamiltonianPointConfig" begin + Test.@testset "scalar initial condition returns tuple of scalars" begin + sys = Systems.HamiltonianVectorFieldSystem( + Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + ) + result = FakeIntegrationResult([[1.0, 0.5], [0.5, 0.25]]) + config = Configs.HamiltonianPointConfig(0.0, 1.0, 0.5, 1.0) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output == (0.5, 0.25) + Test.@test typeof(config) == Configs.HamiltonianPointConfig{Float64, Float64, Float64, Float64} + end + + Test.@testset "vector initial condition returns tuple of vectors" begin + sys = Systems.HamiltonianVectorFieldSystem( + Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + ) + result = FakeIntegrationResult([[1.0, 2.0, 0.5, 0.3], [0.5, 1.0, 0.25, 0.15]]) + config = Configs.HamiltonianPointConfig(0.0, [1.0, 2.0], [0.5, 0.3], 1.0) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output == ([0.5, 1.0], [0.25, 0.15]) + Test.@test typeof(config) <: Configs.HamiltonianPointConfig{Float64, <:AbstractVector, <:AbstractVector, Float64} + end + + Test.@testset "vector initial condition uses correct dimension split" begin + # Test the bug fix: should use length(initial_state) not length(initial_condition) + sys = Systems.HamiltonianVectorFieldSystem( + Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + ) + # Final state has 6 elements: 3 state + 3 costate + result = FakeIntegrationResult([[1.0, 2.0, 3.0, 0.5, 0.6, 0.7], [0.5, 1.0, 1.5, 0.25, 0.3, 0.35]]) + config = Configs.HamiltonianPointConfig(0.0, [1.0, 2.0, 3.0], [0.5, 0.6, 0.7], 1.0) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + # Should split into first 3 (state) and last 3 (costate) + Test.@test output == ([0.5, 1.0, 1.5], [0.25, 0.3, 0.35]) + end + end + + # ==================================================================== + # UNIT TESTS - build_solution for HamiltonianTrajectoryConfig + # ==================================================================== + + Test.@testset "build_solution - HamiltonianTrajectoryConfig" begin + Test.@testset "returns HamiltonianVectorFieldSolution wrapping result" begin + sys = Systems.HamiltonianVectorFieldSystem( + Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + ) + result = FakeIntegrationResult([[1.0, 2.0, 0.5, 0.3], [0.5, 1.0, 0.25, 0.15]]) + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 2.0], [0.5, 0.3]) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "HamiltonianVectorFieldSolution contains correct result" begin + sys = Systems.HamiltonianVectorFieldSystem( + Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + ) + result = FakeIntegrationResult([[1.0, 2.0, 0.5, 0.3], [0.5, 1.0, 0.25, 0.15]]) + config = Configs.HamiltonianTrajectoryConfig((0.0, 1.0), [1.0, 2.0], [0.5, 0.3]) + + output = Solutions.build_solution(Configs.mode_trait(config), Configs.content_trait(config), config, result) + Test.@test output.result === result + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "build_solution is exported" begin + Test.@test isdefined(Solutions, :build_solution) + end + + Test.@testset "HamiltonianVectorFieldSolution is exported" begin + Test.@test isdefined(Solutions, :HamiltonianVectorFieldSolution) + end + end + end +end + +end # module + +test_building_solutions() = TestBuildingSolutions.test_building_solutions() \ No newline at end of file diff --git a/test/suite/solutions/test_hamiltonian_vector_field_solution.jl b/test/suite/solutions/test_hamiltonian_vector_field_solution.jl new file mode 100644 index 00000000..e8c27054 --- /dev/null +++ b/test/suite/solutions/test_hamiltonian_vector_field_solution.jl @@ -0,0 +1,238 @@ +module TestHamiltonianVectorFieldSolution + +import Test +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# ============================================================================= +# Fake integration result for testing +# ============================================================================= + +struct FakeHamiltonianResult <: Integrators.AbstractIntegrationResult + final_u::Vector{Float64} + ts::Vector{Float64} + us::Vector{Vector{Float64}} +end + +# ============================================================================= +# Fake HamiltonianVectorFieldSolution for testing plot stub +# ============================================================================= + +struct FakeHamiltonianVectorFieldSolution <: Solutions.AbstractHamiltonianVectorFieldSolution + data::String +end + +Integrators.final_state(r::FakeHamiltonianResult) = r.final_u +Integrators.times(r::FakeHamiltonianResult) = r.ts +function Integrators.evaluate_at(r::FakeHamiltonianResult, t::Real) + idx = findfirst(โ‰ฅ(t), r.ts) + if isnothing(idx) + idx = length(r.ts) + end + return r.us[idx] +end + +# Implement merge for FakeHamiltonianResult to support merge tests +function Integrators.merge(results::AbstractVector{<:FakeHamiltonianResult}) + if isempty(results) + throw(Exceptions.IncorrectArgument( + "Cannot merge empty sequence of FakeHamiltonianResult"; + got = "0 results", + expected = "at least 1 result", + context = "FakeHamiltonianResult merge", + )) + end + # Combine times and states from all results + all_ts = vcat([r.ts for r in results]...) + all_us = vcat([r.us for r in results]...) + # Use final_u from the last result + final_u = results[end].final_u + return FakeHamiltonianResult(final_u, all_ts, all_us) +end + +function test_hamiltonian_vector_field_solution() + Test.@testset "Hamiltonian Vector Field Solution Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + Test.@test sol isa Solutions.AbstractHamiltonianVectorFieldSolution + end + + # ==================================================================== + # UNIT TESTS - sol(t) returns tuple + # ==================================================================== + + Test.@testset "sol(t) returns tuple" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + x, p = sol(0.0) + Test.@test x == [1.0, 2.0] + Test.@test p == [3.0, 4.0] + + x, p = sol(0.5) + Test.@test x == [1.5, 2.5] + Test.@test p == [3.5, 4.5] + + x, p = sol(1.0) + Test.@test x == [2.0, 3.0] + Test.@test p == [4.0, 5.0] + end + + # ==================================================================== + # UNIT TESTS - state and costate accessors + # ==================================================================== + + Test.@testset "state and costate accessors" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + x_func = Solutions.state(sol) + Test.@test x_func isa Function + Test.@test x_func(0.0) == [1.0, 2.0] + + p_func = Solutions.costate(sol) + Test.@test p_func isa Function + Test.@test p_func(0.0) == [3.0, 4.0] + end + + # ==================================================================== + # UNIT TESTS - final_state + # ==================================================================== + + Test.@testset "final_state" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + x, p = Integrators.final_state(sol) + Test.@test x == [1.0, 2.0] + Test.@test p == [3.0, 4.0] + end + + # ==================================================================== + # UNIT TESTS - time_grid + # ==================================================================== + + Test.@testset "time_grid" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + tg = Solutions.time_grid(sol) + Test.@test tg == [0.0, 0.5, 1.0] + end + + # ==================================================================== + # UNIT TESTS - Integrators.merge + # ==================================================================== + + Test.@testset "Integrators.merge" begin + Test.@testset "merge single segment" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + merged = Integrators.merge([sol]) + Test.@test merged isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "merge multiple segments" begin + result1 = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5]]) + result2 = FakeHamiltonianResult([1.5, 2.5, 3.5, 4.5], [0.5, 1.0], [[2, 3, 4, 5], [2.5, 3.5, 4.5, 5.5]]) + x0 = [1.0, 2.0] # initial state + sol1 = Solutions.HamiltonianVectorFieldSolution(x0, result1) + sol2 = Solutions.HamiltonianVectorFieldSolution(x0, result2) + + merged = Integrators.merge([sol1, sol2]) + Test.@test merged isa Solutions.HamiltonianVectorFieldSolution + end + + Test.@testset "merge throws on empty sequence" begin + Test.@test_throws Exceptions.IncorrectArgument Integrators.merge(Solutions.HamiltonianVectorFieldSolution[]) + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + Test.@testset "text/plain format" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + io = IOBuffer() + show(io, MIME("text/plain"), sol) + output = String(take!(io)) + Test.@test occursin("HamiltonianVectorFieldSolution", output) + end + + Test.@testset "compact format" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], [0.0, 0.5, 1.0], [[1, 2, 3, 4], [1.5, 2.5, 3.5, 4.5], [2, 3, 4, 5]]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + io = IOBuffer() + show(io, sol) + output = String(take!(io)) + Test.@test occursin("HamiltonianVectorFieldSolution", output) + end + + Test.@testset "show handles empty times gracefully" begin + result = FakeHamiltonianResult([1.0, 2.0, 3.0, 4.0], Float64[], Vector{Vector{Float64}}[]) + x0 = [1.0, 2.0] # initial state + sol = Solutions.HamiltonianVectorFieldSolution(x0, result) + + io = IOBuffer() + show(io, MIME("text/plain"), sol) + output = String(take!(io)) + Test.@test occursin("HamiltonianVectorFieldSolution", output) + end + end + + # ==================================================================== + # UNIT TESTS - Plot stub + # ==================================================================== + + Test.@testset "Plot stub" begin + Test.@testset "throws ExtensionError without Plots extension" begin + fake_sol = FakeHamiltonianVectorFieldSolution("test data") + + Test.@test_throws Exceptions.ExtensionError Solutions.plot(fake_sol) + end + + Test.@testset "error message mentions Plots extension" begin + fake_sol = FakeHamiltonianVectorFieldSolution("test data") + + try + Solutions.plot(fake_sol) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.ExtensionError + msg = sprint(showerror, err) + Test.@test occursin("Plots", msg) + end + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_hamiltonian_vector_field_solution() = TestHamiltonianVectorFieldSolution.test_hamiltonian_vector_field_solution() diff --git a/test/suite/solutions/test_hamiltonian_vf_solution_shapes.jl b/test/suite/solutions/test_hamiltonian_vf_solution_shapes.jl new file mode 100644 index 00000000..68e01216 --- /dev/null +++ b/test/suite/solutions/test_hamiltonian_vf_solution_shapes.jl @@ -0,0 +1,232 @@ +module TestHamiltonianVFSolutionShapes + +import Test +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Solutions +import CTFlows.Common +import CTFlows.Data +import CTFlows.Traits + +using SciMLBase: SciMLBase +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +using StaticArrays: SA, SVector, MVector, SMatrix + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Reference systems for numerical testing +# ============================================================================== + +# Harmonic oscillator: x' = p, p' = -x +const HVF_HARMONIC = Data.HamiltonianVectorField((x, p) -> (p, -x); is_autonomous=true, is_variable=false) +const HSYS = Systems.HamiltonianVectorFieldSystem(HVF_HARMONIC) +const INTEG = Integrators.SciML() +const ATOL = 1e-5 + +# ============================================================================== +# Test function +# ============================================================================== + +function test_hamiltonian_vf_solution_shapes() + Test.@testset "HamiltonianVectorFieldSolution Shape Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # SHAPE TESTS - sol(t) for HamiltonianVectorFieldSolution + # ==================================================================== + + Test.@testset "sol(t) shape preservation" begin + Test.@testset "scalar x0, p0 โ†’ scalar output" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + x, p = sol(ฯ€/2) + Test.@test x isa Real + Test.@test p isa Real + Test.@test x โ‰ˆ 0.0 atol=ATOL + Test.@test p โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "vector x0, p0 โ†’ vector output" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + x, p = sol(ฯ€/2) + Test.@test x isa AbstractVector && length(x) == 2 + Test.@test p isa AbstractVector && length(p) == 2 + Test.@test x โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test p โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "SVector x0, p0 โ†’ vector output" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), SA[1.0, 0.0], SA[0.0, 1.0]) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + x, p = sol(ฯ€/2) + Test.@test x isa AbstractVector + Test.@test p isa AbstractVector + Test.@test x โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test p โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "matrix x0, p0 โ†’ matrix output" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = [1.0 2.0; 3.0 4.0] + P0 = [0.0 0.0; 1.0 1.0] + sol = hflow((0.0, ฯ€/2), X0, P0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + X, P = sol(ฯ€/2) + Test.@test X isa AbstractMatrix + Test.@test P isa AbstractMatrix + Test.@test size(X) == (2, 2) + Test.@test size(P) == (2, 2) + Test.@test X โ‰ˆ P0 atol=ATOL + Test.@test P โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "SMatrix x0, p0 โ†’ matrix output" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = SMatrix{2,2}(1.0, 3.0, 2.0, 4.0) + P0 = SMatrix{2,2}(0.0, 1.0, 0.0, 1.0) + sol = hflow((0.0, ฯ€/2), X0, P0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + X, P = sol(ฯ€/2) + Test.@test X isa AbstractMatrix + Test.@test P isa AbstractMatrix + Test.@test X โ‰ˆ P0 atol=ATOL + Test.@test P โ‰ˆ -X0 atol=ATOL + end + + Test.@testset "complex scalar x0, p0 โ†’ complex scalar output" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0+2.0im, 0.0+0.0im) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + x, p = sol(ฯ€/2) + Test.@test x isa Complex + Test.@test p isa Complex + Test.@test x โ‰ˆ 0.0+0.0im atol=ATOL + Test.@test p โ‰ˆ -(1.0+2.0im) atol=ATOL + end + + Test.@testset "complex vector x0, p0 โ†’ complex vector output" begin + hflow = Flows.build_flow(HSYS, INTEG) + x0 = [1.0+2.0im, 0.0+0.0im] + p0 = [0.0+0.0im, 1.0+1.0im] + sol = hflow((0.0, ฯ€/2), x0, p0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + x, p = sol(ฯ€/2) + Test.@test x isa AbstractVector + Test.@test p isa AbstractVector + Test.@test x โ‰ˆ p0 atol=ATOL + Test.@test p โ‰ˆ -x0 atol=ATOL + end + + Test.@testset "complex matrix x0, p0 โ†’ complex matrix output" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + P0 = [0.0+0.0im 1.0+1.0im; 2.0+2.0im 3.0+3.0im] + sol = hflow((0.0, ฯ€/2), X0, P0) + Test.@test sol isa Solutions.HamiltonianVectorFieldSolution + + # Test sol(t) at t=ฯ€/2 + X, P = sol(ฯ€/2) + Test.@test X isa AbstractMatrix + Test.@test P isa AbstractMatrix + Test.@test X โ‰ˆ P0 atol=ATOL + Test.@test P โ‰ˆ -X0 atol=ATOL + end + end + + # ==================================================================== + # SHAPE TESTS - final time evaluation + # ==================================================================== + + Test.@testset "final time evaluation shape preservation" begin + Test.@testset "scalar x0, p0 โ†’ scalar output" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + + x, p = sol(ฯ€/2) + Test.@test x isa Real + Test.@test p isa Real + Test.@test x โ‰ˆ 0.0 atol=ATOL + Test.@test p โ‰ˆ -1.0 atol=ATOL + end + + Test.@testset "vector x0, p0 โ†’ vector output" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + + x, p = sol(ฯ€/2) + Test.@test x isa AbstractVector && length(x) == 2 + Test.@test p isa AbstractVector && length(p) == 2 + Test.@test x โ‰ˆ [0.0, 1.0] atol=ATOL + Test.@test p โ‰ˆ [-1.0, 0.0] atol=ATOL + end + + Test.@testset "matrix x0, p0 โ†’ matrix output" begin + hflow = Flows.build_flow(HSYS, INTEG) + X0 = [1.0 2.0; 3.0 4.0] + P0 = [0.0 0.0; 1.0 1.0] + sol = hflow((0.0, ฯ€/2), X0, P0) + + X, P = sol(ฯ€/2) + Test.@test X isa AbstractMatrix + Test.@test P isa AbstractMatrix + Test.@test size(X) == (2, 2) + Test.@test size(P) == (2, 2) + Test.@test X โ‰ˆ P0 atol=ATOL + Test.@test P โ‰ˆ -X0 atol=ATOL + end + end + + # ==================================================================== + # SHAPE TESTS - intermediate times + # ==================================================================== + + Test.@testset "intermediate time evaluation" begin + Test.@testset "scalar x0, p0 at t=ฯ€/4" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), 1.0, 0.0) + + # At t=ฯ€/4: x = cos(ฯ€/4) โ‰ˆ 0.707, p = -sin(ฯ€/4) โ‰ˆ -0.707 + x, p = sol(ฯ€/4) + Test.@test x isa Real + Test.@test p isa Real + Test.@test x โ‰ˆ 0.7071 atol=ATOL + Test.@test p โ‰ˆ -0.7071 atol=ATOL + end + + Test.@testset "vector x0, p0 at t=ฯ€/4" begin + hflow = Flows.build_flow(HSYS, INTEG) + sol = hflow((0.0, ฯ€/2), [1.0, 0.0], [0.0, 1.0]) + + x, p = sol(ฯ€/4) + Test.@test x isa AbstractVector && length(x) == 2 + Test.@test p isa AbstractVector && length(p) == 2 + Test.@test x โ‰ˆ [0.7071, 0.7071] atol=ATOL + Test.@test p โ‰ˆ [-0.7071, 0.7071] atol=ATOL + end + end + end +end + +end # module + +test_hamiltonian_vf_solution_shapes() = TestHamiltonianVFSolutionShapes.test_hamiltonian_vf_solution_shapes() diff --git a/test/suite/solutions/test_solutions_module.jl b/test/suite/solutions/test_solutions_module.jl new file mode 100644 index 00000000..ca2b22f3 --- /dev/null +++ b/test/suite/solutions/test_solutions_module.jl @@ -0,0 +1,48 @@ +module TestSolutionsModule + +import Test +import CTFlows.Solutions + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_solutions_module() + Test.@testset "Solutions Module Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "VectorFieldSolution is exported" begin + Test.@test isdefined(Solutions, :VectorFieldSolution) + end + + Test.@testset "build_solution is exported" begin + Test.@test isdefined(Solutions, :build_solution) + end + end + + # ==================================================================== + # UNIT TESTS - Module Loading + # ==================================================================== + + Test.@testset "Module Loading" begin + Test.@testset "Solutions module exists" begin + Test.@test @isdefined(Solutions) + end + + Test.@testset "Solutions is a Module" begin + Test.@test Solutions isa Module + end + end + end +end + +end # module + +test_solutions_module() = TestSolutionsModule.test_solutions_module() \ No newline at end of file diff --git a/test/suite/solutions/test_vector_field_solution.jl b/test/suite/solutions/test_vector_field_solution.jl new file mode 100644 index 00000000..3f06b14a --- /dev/null +++ b/test/suite/solutions/test_vector_field_solution.jl @@ -0,0 +1,162 @@ +module TestVectorFieldSolution + +import Test +import CTFlows.Solutions +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Integrators + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for testing +# ============================================================================== + +""" +Fake integration result for testing VectorFieldSolution callable interface. +""" +struct FakeIntegrationResult <: Integrators.AbstractIntegrationResult + t::Vector{Float64} + u::Vector{Vector{Float64}} +end + +Integrators.times(r::FakeIntegrationResult) = r.t +Integrators.final_state(r::FakeIntegrationResult) = r.u[end] +function Integrators.evaluate_at(r::FakeIntegrationResult, t::Real) + # Simple interpolation for testing + return r.u[1] +end + +""" +Fake VectorFieldSolution for testing stub methods on AbstractVectorFieldSolution. +""" +struct FakeVectorFieldSolution <: Solutions.AbstractVectorFieldSolution + data::String +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_vector_field_solution() + Test.@testset "VectorFieldSolution Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + Test.@testset "constructs from integration result" begin + result = FakeIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(result) + Test.@test sol isa Solutions.VectorFieldSolution + end + end + + # ==================================================================== + # UNIT TESTS - Callable & Delegation + # ==================================================================== + + Test.@testset "Callable & Delegation" begin + Test.@testset "delegates evaluate_at to result" begin + result = FakeIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(result) + Test.@test sol(0.5) === Integrators.evaluate_at(result, 0.5) + end + + Test.@testset "delegates times to result" begin + result = FakeIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(result) + Test.@test Integrators.times(sol) === Integrators.times(result) + end + + Test.@testset "state accessor returns sol itself" begin + result = FakeIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(result) + x = Solutions.state(sol) + Test.@test x === sol # Returns same object + end + + Test.@testset "state accessor is callable" begin + result = FakeIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(result) + x = Solutions.state(sol) + Test.@test x(0.5) โ‰ˆ Integrators.evaluate_at(result, 0.5) + end + + Test.@testset "time_grid alias works" begin + result = FakeIntegrationResult([0.0, 0.5, 1.0], [[1.0], [0.5], [0.25]]) + sol = Solutions.VectorFieldSolution(result) + tg = Solutions.time_grid(sol) + ts = Integrators.times(sol) + Test.@test tg === ts # Returns same object + end + end + + # ==================================================================== + # UNIT TESTS - Plot stub + # ==================================================================== + + Test.@testset "Plot stub" begin + Test.@testset "throws ExtensionError without Plots extension" begin + fake_sol = FakeVectorFieldSolution("test data") + + Test.@test_throws Exceptions.ExtensionError Solutions.plot(fake_sol) + end + + Test.@testset "error message mentions Plots extension" begin + fake_sol = FakeVectorFieldSolution("test data") + + try + Solutions.plot(fake_sol) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.ExtensionError + msg = sprint(showerror, err) + Test.@test occursin("Plots", msg) + end + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + Test.@testset "MIME text/plain" begin + result = FakeIntegrationResult([0.0, 1.0], [[1.0, 2.0], [0.5, 1.0]]) + sol = Solutions.VectorFieldSolution(result) + + io = IOBuffer() + show(io, MIME("text/plain"), sol) + output = String(take!(io)) + Test.@test occursin("VectorFieldSolution", output) + end + + Test.@testset "compact" begin + result = FakeIntegrationResult([0.0, 1.0], [[1.0, 2.0], [0.5, 1.0]]) + sol = Solutions.VectorFieldSolution(result) + + io = IOBuffer() + show(io, sol) + output = String(take!(io)) + Test.@test occursin("VectorFieldSolution", output) + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "VectorFieldSolution is exported" begin + Test.@test isdefined(Solutions, :VectorFieldSolution) + end + end + end +end + +end # module + +test_vector_field_solution() = TestVectorFieldSolution.test_vector_field_solution() \ No newline at end of file diff --git a/test/suite/systems/test_abstract_system.jl b/test/suite/systems/test_abstract_system.jl new file mode 100644 index 00000000..5217dda5 --- /dev/null +++ b/test/suite/systems/test_abstract_system.jl @@ -0,0 +1,228 @@ +module TestAbstractSystem + +import Test +import CTBase.Exceptions +import CTFlows.Systems +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake system for testing the AbstractSystem contract. + +This minimal implementation provides the required contract methods to test +routing and default behavior without full system complexity. +""" +struct FakeSystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +# Implement contract: rhs +function Systems.rhs(sys::FakeSystem) + return (du, u, p, t) -> du .= sys.data .* u +end + +# Fake subtypes for hierarchy testing +struct FakeStateSystem <: Systems.AbstractStateSystem{Traits.Autonomous, Traits.Fixed} + data::Vector{Float64} +end + +function Systems.rhs(sys::FakeStateSystem) + return (du, u, p, t) -> du .= sys.data .* u +end + +struct FakeHamiltonianSystem <: Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithoutAD} + data::Vector{Float64} +end + +function Systems.rhs(sys::FakeHamiltonianSystem) + return (du, u, p, t) -> du .= sys.data .* u +end + +""" +Minimal system that does not implement the contract (for error testing). +""" +struct MinimalSystem <: Systems.AbstractSystem{Traits.Autonomous, Traits.Fixed} + state_dim::Int +end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_system() + Test.@testset "Abstract System Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + Test.@test FakeSystem([1.0, 2.0]) isa Systems.AbstractSystem + Test.@test MinimalSystem(2) isa Systems.AbstractSystem + end + + Test.@testset "Hierarchy" begin + Test.@test FakeStateSystem([1.0, 2.0]) isa Systems.AbstractStateSystem + Test.@test FakeStateSystem([1.0, 2.0]) isa Systems.AbstractSystem + Test.@test FakeHamiltonianSystem([1.0, 2.0]) isa Systems.AbstractHamiltonianSystem + Test.@test FakeHamiltonianSystem([1.0, 2.0]) isa Systems.AbstractSystem + # Verify the two subtypes are not related to each other + Test.@test !(FakeStateSystem([1.0, 2.0]) isa Systems.AbstractHamiltonianSystem) + Test.@test !(FakeHamiltonianSystem([1.0, 2.0]) isa Systems.AbstractStateSystem) + end + + # ==================================================================== + # UNIT TESTS - Contract Implementation + # ==================================================================== + + Test.@testset "Contract Implementation" begin + sys = FakeSystem([1.0, 2.0]) + + Test.@testset "rhs returns callable" begin + rhs = Systems.rhs(sys) + Test.@test rhs isa Function + end + + Test.@testset "rhs function has correct signature (du, u, p, t)" begin + rhs = Systems.rhs(sys) + du = zeros(2) + u = [3.0, 4.0] + p = [] + t = 0.0 + # Should not throw - signature is correct + rhs(du, u, p, t) + Test.@test du โ‰ˆ [3.0, 8.0] atol=1e-10 + end + + Test.@testset "rhs function fills du in place" begin + rhs = Systems.rhs(sys) + du = zeros(2) + rhs(du, [3.0, 4.0], [], 0.0) + Test.@test du โ‰ˆ [3.0, 8.0] atol=1e-10 + end + + Test.@testset "rhs function uses system data" begin + sys1 = FakeSystem([2.0, 3.0]) + sys2 = FakeSystem([0.5, 1.0]) + rhs1 = Systems.rhs(sys1) + rhs2 = Systems.rhs(sys2) + du1 = zeros(2) + du2 = zeros(2) + rhs1(du1, [1.0, 1.0], [], 0.0) + rhs2(du2, [1.0, 1.0], [], 0.0) + Test.@test du1 โ‰ˆ [2.0, 3.0] atol=1e-10 + Test.@test du2 โ‰ˆ [0.5, 1.0] atol=1e-10 + end + end + + # ==================================================================== + # UNIT TESTS - Trait Methods + # ==================================================================== + + Test.@testset "Trait Methods" begin + sys = FakeSystem([1.0, 2.0]) + sys2 = MinimalSystem(3) + + Test.@testset "has_time_dependence_trait returns true" begin + Test.@test Traits.has_time_dependence_trait(sys) === true + Test.@test Traits.has_time_dependence_trait(sys2) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + Test.@test Traits.has_variable_dependence_trait(sys) === true + Test.@test Traits.has_variable_dependence_trait(sys2) === true + end + + Test.@testset "time_dependence extracts trait from type parameter" begin + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.time_dependence(sys2) === Traits.Autonomous + end + + Test.@testset "variable_dependence extracts trait from type parameter" begin + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + Test.@test Traits.variable_dependence(sys2) === Traits.Fixed + end + + Test.@testset "trait methods work for all AbstractSystem subtypes" begin + # Verify that the trait methods work for any AbstractSystem subtype + for sys_instance in [sys, sys2] + Test.@test Traits.has_time_dependence_trait(sys_instance) === true + Test.@test Traits.has_variable_dependence_trait(sys_instance) === true + end + end + end + + # ==================================================================== + # UNIT TESTS - NotImplemented Errors + # ==================================================================== + + Test.@testset "NotImplemented Errors" begin + sys = MinimalSystem(2) + + Test.@testset "rhs throws NotImplemented" begin + try + Systems.rhs(sys) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("rhs", err.msg) + end + end + + Test.@testset "rhs_oop throws NotImplemented" begin + try + Systems.rhs_oop(sys) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("rhs_oop", err.msg) + end + end + + Test.@testset "rhs_oop with explicit Bool throws NotImplemented" begin + try + Systems.rhs_oop(sys, false) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test occursin("rhs_oop", err.msg) + Test.@test occursin("AbstractSystem", err.context) + end + end + + Test.@testset "NotImplemented error contains required fields" begin + try + Systems.rhs(sys) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.NotImplemented + Test.@test hasfield(typeof(err), :msg) + Test.@test hasfield(typeof(err), :context) + end + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Systems, :AbstractSystem) + Test.@test isdefined(Systems, :AbstractStateSystem) + Test.@test isdefined(Systems, :AbstractHamiltonianSystem) + end + end + end +end + +end # module + +test_abstract_system() = TestAbstractSystem.test_abstract_system() diff --git a/test/suite/systems/test_building_systems.jl b/test/suite/systems/test_building_systems.jl new file mode 100644 index 00000000..72810b47 --- /dev/null +++ b/test/suite/systems/test_building_systems.jl @@ -0,0 +1,97 @@ +module TestBuildingSystems + +import Test +import CTFlows.Systems +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_building_systems() + Test.@testset "Building Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - build_system function + # ==================================================================== + + Test.@testset "build_system" begin + Test.@testset "build_system returns VectorFieldSystem" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.build_system(vf) + Test.@test sys isa Systems.VectorFieldSystem + Test.@test sys isa Systems.AbstractSystem + end + + Test.@testset "build_system preserves traits - Autonomous Fixed" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.build_system(vf) + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + end + + Test.@testset "build_system preserves traits - NonAutonomous Fixed" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + sys = Systems.build_system(vf) + Test.@test Traits.time_dependence(sys) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + end + + Test.@testset "build_system preserves traits - Autonomous NonFixed" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + sys = Systems.build_system(vf) + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.variable_dependence(sys) === Traits.NonFixed + end + + Test.@testset "build_system preserves traits - NonAutonomous NonFixed" begin + vf = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + sys = Systems.build_system(vf) + Test.@test Traits.time_dependence(sys) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(sys) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Integration with rhs + # ==================================================================== + + Test.@testset "Integration with rhs" begin + Test.@testset "built system has working rhs" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.build_system(vf) + rhs = Systems.rhs(sys) + Test.@test rhs isa Function + end + + Test.@testset "built system rhs computes correctly" begin + vf = Data.VectorField(x -> 2 .* x; is_autonomous=true, is_variable=false) + sys = Systems.build_system(vf) + rhs = Systems.rhs(sys) + du = zeros(2) + p = Common.ODEParameters(nothing) + rhs(du, [1.0, 2.0], p, 0.0) + Test.@test du โ‰ˆ [2.0, 4.0] atol=1e-10 + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported function" begin + Test.@test isdefined(Systems, :build_system) + end + end + end +end + +end # module + +test_building_systems() = TestBuildingSystems.test_building_systems() \ No newline at end of file diff --git a/test/suite/systems/test_hamiltonian_system.jl b/test/suite/systems/test_hamiltonian_system.jl new file mode 100644 index 00000000..56c5d3c7 --- /dev/null +++ b/test/suite/systems/test_hamiltonian_system.jl @@ -0,0 +1,243 @@ +module TestHamiltonianSystem + +import Test +import CTBase.Exceptions +import CTFlows.Data: Data +import CTFlows.Common: Common +import CTFlows.Traits: Traits +import CTFlows.Systems: Systems +import CTFlows.Differentiation: Differentiation + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# Fake AD backend for testing (no actual AD, just a stub) +struct FakeADBackend <: Differentiation.AbstractADBackend end + +function Differentiation.hamiltonian_gradient(backend::FakeADBackend, h, t, x, p, v, cache) + # Fake gradient: โˆ‚H/โˆ‚x = x, โˆ‚H/โˆ‚p = p (for H = 0.5*(x^2 + p^2)) + return (x, p) +end + +function Differentiation.variable_gradient(backend::FakeADBackend, h, t, x, p, v, cache) + # Fake gradient: โˆ‚H/โˆ‚v = v (for H = 0.5*v^2), or 0.0 if v is nothing + return v === nothing ? 0.0 : v +end + +function test_hamiltonian_system() + Test.@testset "Hamiltonian System Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) + backend = FakeADBackend() + + # Build system without state_dimension (lazy inference) + sys = Systems.HamiltonianSystem(h, backend) + Test.@test sys isa Systems.HamiltonianSystem + Test.@test sys isa Systems.AbstractHamiltonianSystem + end + + # ==================================================================== + # UNIT TESTS - ad_trait + # ==================================================================== + + Test.@testset "ad_trait" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) + backend = FakeADBackend() + sys = Systems.HamiltonianSystem(h, backend) + + Test.@test Traits.ad_trait(sys) === Traits.WithAD + end + + # ==================================================================== + # UNIT TESTS - build_rhs (lazy in-place) + # ==================================================================== + + Test.@testset "build_rhs" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) + backend = FakeADBackend() + sys = Systems.HamiltonianSystem(h, backend) + + x0 = [1.0, 2.0] + p0 = [3.0, 4.0] + rhs = Systems.build_rhs(sys, x0, p0) + Test.@test rhs isa Function + + # Test RHS call with vector + u = [1.0, 2.0, 3.0, 4.0] # x = [1, 2], p = [3, 4] + du = zeros(4) + p = Common.ODEParameters(nothing) + rhs(du, u, p, 0.0) + + # โˆ‚H/โˆ‚x = x = [1, 2], โˆ‚H/โˆ‚p = p = [3, 4], so du = [โˆ‚p, -โˆ‚x] = [3, 4, -1, -2] + Test.@test du[1:2] == [3.0, 4.0] + Test.@test du[3:4] == [-1.0, -2.0] + + # Test RHS call with matrix + x0_mat = [1.0 2.0; 3.0 4.0] + p0_mat = [5.0 6.0; 7.0 8.0] + rhs_mat = Systems.build_rhs(sys, x0_mat, p0_mat) + u_mat = [1.0 2.0; 3.0 4.0; 5.0 6.0; 7.0 8.0] # x = [1 2; 3 4], p = [5 6; 7 8] + du_mat = zeros(4, 2) + rhs_mat(du_mat, u_mat, p, 0.0) + Test.@test du_mat โ‰ˆ [5.0 6.0; 7.0 8.0; -1.0 -2.0; -3.0 -4.0] atol=1e-10 + end + + # ==================================================================== + # UNIT TESTS - build_oop_rhs (lazy out-of-place) + # ==================================================================== + + Test.@testset "build_oop_rhs" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) + backend = FakeADBackend() + sys = Systems.HamiltonianSystem(h, backend) + + x0 = [1.0, 2.0] + p0 = [3.0, 4.0] + rhs_oop = Systems.build_oop_rhs(sys, x0, p0) + Test.@test rhs_oop isa Function + + # Test OOP call with vector + u = [1.0, 2.0, 3.0, 4.0] # x = [1, 2], p = [3, 4] + p = Common.ODEParameters(nothing) + du = rhs_oop(u, p, 0.0) + + # โˆ‚H/โˆ‚x = x = [1, 2], โˆ‚H/โˆ‚p = p = [3, 4], so du = vcat(โˆ‚p, -โˆ‚x) = [3, 4, -1, -2] + Test.@test du == [3.0, 4.0, -1.0, -2.0] + end + + # ==================================================================== + # UNIT TESTS - build_rhs_augmented + # ==================================================================== + + Test.@testset "build_rhs_augmented" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2) + 0.5 * v^2; is_autonomous=true, is_variable=false) + backend = FakeADBackend() + sys = Systems.HamiltonianSystem(h, backend) + + # Vector case (n_x=2, n_v=1) + rhs_aug = Systems.build_rhs_augmented(sys, 2, 1) + Test.@test rhs_aug isa Function + + u = [1.0, 2.0, 3.0, 4.0, 0.5] # x = [1, 2], p = [3, 4], pv = [0.5] + du = zeros(5) + p = Common.ODEParameters(0.5) # variable is 0.5 (matches pv in u) + rhs_aug(du, u, p, 0.0) + + # โˆ‚H/โˆ‚x = x = [1, 2], โˆ‚H/โˆ‚p = p = [3, 4], โˆ‚H/โˆ‚v = v = 0.5 + # du = [โˆ‚p, -โˆ‚x, -โˆ‚v] = [3, 4, -1, -2, -0.5] + Test.@test du[1:2] == [3.0, 4.0] + Test.@test du[3:4] == [-1.0, -2.0] + Test.@test du[5] == -0.5 + + # Matrix compatible case (10ร—3, v matrice 1ร—3) + u_mat = [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0; 10.0 11.0 12.0; 0.5 0.6 0.7] + du_mat = zeros(5, 3) + p_mat = Common.ODEParameters([0.5 0.6 0.7]) # v as matrix with 3 columns + rhs_aug(du_mat, u_mat, p_mat, 0.0) + Test.@test size(du_mat, 2) == 3 # no error, compatible + + # Matrix incompatible case (10ร—3, v matrice 1ร—2) โ†’ PreconditionError + u_mat2 = [1.0 2.0; 4.0 5.0; 7.0 8.0; 10.0 11.0; 0.5 0.6] + du_mat2 = zeros(5, 2) + p_mat2 = Common.ODEParameters([0.5 0.6 0.7]) # v has 3 columns, u has 2 + Test.@test_throws Exceptions.PreconditionError rhs_aug(du_mat2, u_mat2, p_mat2, 0.0) + + # u Matrix, v Vector (no-op check) + u_mat3 = [1.0 2.0; 4.0 5.0; 7.0 8.0; 10.0 11.0; 0.5 0.6] + du_mat3 = zeros(5, 2) + p_vec = Common.ODEParameters(0.5) # v as scalar (not a matrix) + rhs_aug(du_mat3, u_mat3, p_vec, 0.0) # no error, no-op check + end + + # ==================================================================== + # UNIT TESTS - _aug_split + # ==================================================================== + + Test.@testset "_aug_split" begin + # Vector case + u_vec = [1.0, 2.0, 3.0, 4.0, 0.5] + x, p, pv = Systems._aug_split(u_vec, 2, 1) + Test.@test x == [1.0, 2.0] + Test.@test p == [3.0, 4.0] + Test.@test pv == [0.5] + + # Matrix case + u_mat = [1.0 2.0; 3.0 4.0; 5.0 6.0; 7.0 8.0; 0.5 0.6] + x_mat, p_mat, pv_mat = Systems._aug_split(u_mat, 2, 1) + Test.@test x_mat == [1.0 2.0; 3.0 4.0] + Test.@test p_mat == [5.0 6.0; 7.0 8.0] + Test.@test pv_mat == [0.5 0.6] + end + + # ==================================================================== + # UNIT TESTS - _check_aug_batch_compat + # ==================================================================== + + Test.@testset "_check_aug_batch_compat" begin + # Compatible matrices + u = [1.0 2.0; 3.0 4.0] + v = [0.5 0.6] + Test.@test Systems._check_aug_batch_compat(u, v) === nothing # no error + + # Incompatible matrices + u2 = [1.0 2.0; 3.0 4.0] + v2 = [0.5 0.6 0.7] + Test.@test_throws Exceptions.PreconditionError Systems._check_aug_batch_compat(u2, v2) + + # Non-matrix cases (no-op) + u3 = [1.0, 2.0, 3.0] + v3 = [0.5] + Test.@test Systems._check_aug_batch_compat(u3, v3) === nothing + + u4 = [1.0 2.0; 3.0 4.0] + v4 = 0.5 + Test.@test Systems._check_aug_batch_compat(u4, v4) === nothing + end + + # ==================================================================== + # UNIT TESTS - build_system overloads + # ==================================================================== + + Test.@testset "build_system" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) + backend = FakeADBackend() + + # Build system without state_dimension (lazy inference) + sys = Systems.build_system(h, backend) + Test.@test sys isa Systems.HamiltonianSystem + end + + # ==================================================================== + # UNIT TESTS - Type Stability + # ==================================================================== + + Test.@testset "Type Stability" begin + h = Data.Hamiltonian((t, x, p, v) -> 0.5 * sum(x.^2) + sum(p.^2); is_autonomous=true, is_variable=false) + backend = FakeADBackend() + + sys = Systems.HamiltonianSystem(h, backend) + Test.@test Test.@inferred Traits.ad_trait(sys) === Traits.WithAD + end + + # ==================================================================== + # REGRESSION TEST - HamiltonianVectorFieldSystem unaffected + # ==================================================================== + + Test.@testset "Regression: HamiltonianVectorFieldSystem" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + Test.@test sys isa Systems.HamiltonianVectorFieldSystem + Test.@test sys isa Systems.AbstractHamiltonianSystem + Test.@test Traits.ad_trait(sys) === Traits.WithoutAD + end + end +end + +end # module + +test_hamiltonian_system() = TestHamiltonianSystem.test_hamiltonian_system() diff --git a/test/suite/systems/test_hamiltonian_vector_field_system.jl b/test/suite/systems/test_hamiltonian_vector_field_system.jl new file mode 100644 index 00000000..be482e86 --- /dev/null +++ b/test/suite/systems/test_hamiltonian_vector_field_system.jl @@ -0,0 +1,271 @@ +module TestHamiltonianVectorFieldSystem + +import Test +import CTBase.Exceptions +import CTFlows.Data: Data +import CTFlows.Common: Common +import CTFlows.Traits: Traits +import CTFlows.Systems: Systems +import CTFlows.Solutions: Solutions +import StaticArrays: SA, StaticArrays + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +function test_hamiltonian_vector_field_system() + Test.@testset "Hamiltonian Vector Field System Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + # From HamiltonianVectorField (lazy, no dimension) + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + Test.@test sys isa Systems.HamiltonianVectorFieldSystem + Test.@test sys isa Systems.AbstractHamiltonianSystem + + # Hierarchy check: supertype with WithoutAD + Test.@test sys isa Systems.AbstractHamiltonianSystem{Traits.Autonomous, Traits.Fixed, Traits.WithoutAD} + end + + Test.@testset "ad_trait" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + + Test.@test Traits.ad_trait(sys) === Traits.WithoutAD + Test.@test Test.@inferred Traits.ad_trait(sys) === Traits.WithoutAD + end + + # ==================================================================== + # UNIT TESTS - build_system + # ==================================================================== + + Test.@testset "build_system" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + + # Build system without state_dimension (lazy inference) + sys = Systems.build_system(hvf) + Test.@test sys isa Systems.HamiltonianVectorFieldSystem + end + + # ==================================================================== + # UNIT TESTS - build_rhs (lazy in-place) + # ==================================================================== + + Test.@testset "build_rhs" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + + x0 = [1.0, 2.0] + p0 = [3.0, 4.0] + rhs = Systems.build_rhs(sys, x0, p0) + Test.@test rhs isa Function + + # Test RHS call with vector + u = [1.0, 2.0, 3.0, 4.0] # x = [1, 2], p = [3, 4] + du = zeros(4) + p = Common.ODEParameters(nothing) + rhs(du, u, p, 0.0) + + # dx = x = [1, 2], dp = -p = [-3, -4] + Test.@test du[1:2] == [1.0, 2.0] + Test.@test du[3:4] == [-3.0, -4.0] + + # Test RHS call with matrix + x0_mat = [1.0 2.0; 3.0 4.0] + p0_mat = [5.0 6.0; 7.0 8.0] + rhs_mat = Systems.build_rhs(sys, x0_mat, p0_mat) + u_mat = [1.0 2.0; 3.0 4.0; 5.0 6.0; 7.0 8.0] # x = [1 2; 3 4], p = [5 6; 7 8] + du_mat = zeros(4, 2) + rhs_mat(du_mat, u_mat, p, 0.0) + Test.@test du_mat โ‰ˆ [1.0 2.0; 3.0 4.0; -5.0 -6.0; -7.0 -8.0] atol=1e-10 + end + + # ==================================================================== + # UNIT TESTS - build_oop_rhs (lazy out-of-place) + # ==================================================================== + + Test.@testset "build_oop_rhs" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + + x0 = [1.0, 2.0] + p0 = [3.0, 4.0] + rhs_oop = Systems.build_oop_rhs(sys, x0, p0) + Test.@test rhs_oop isa Function + + # Test RHS OOP call with vector + u = [1.0, 2.0, 3.0, 4.0] # x = [1, 2], p = [3, 4] + p = Common.ODEParameters(nothing) + du = rhs_oop(u, p, 0.0) + + # dx = x = [1, 2], dp = -p = [-3, -4] + Test.@test du[1:2] == [1.0, 2.0] + Test.@test du[3:4] == [-3.0, -4.0] + + # Test RHS OOP call with matrix + x0_mat = [1.0 2.0; 3.0 4.0] + p0_mat = [5.0 6.0; 7.0 8.0] + rhs_oop_mat = Systems.build_oop_rhs(sys, x0_mat, p0_mat) + u_mat = [1.0 2.0; 3.0 4.0; 5.0 6.0; 7.0 8.0] # x = [1 2; 3 4], p = [5 6; 7 8] + du_mat = rhs_oop_mat(u_mat, p, 0.0) + Test.@test du_mat โ‰ˆ [1.0 2.0; 3.0 4.0; -5.0 -6.0; -7.0 -8.0] atol=1e-10 + end + + # ==================================================================== + # UNIT TESTS - Complex numbers + # ==================================================================== + + Test.@testset "Complex numbers" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + p_param = Common.ODEParameters(nothing) + + Test.@testset "build_rhs - complex vector" begin + x0 = [1.0+2.0im] + p0 = [3.0+4.0im] + rhs = Systems.build_rhs(sys, x0, p0) + # x = [1+2im], p = [3+4im] โ†’ dx = x, dp = -p + u = [1.0+2.0im, 3.0+4.0im] + du = zeros(ComplexF64, 2) + rhs(du, u, p_param, 0.0) + Test.@test du โ‰ˆ [1.0+2.0im, -3.0-4.0im] atol=1e-10 + end + + Test.@testset "build_oop_rhs - complex vector" begin + x0 = [1.0+2.0im] + p0 = [3.0+4.0im] + rhs_oop = Systems.build_oop_rhs(sys, x0, p0) + u = [1.0+2.0im, 3.0+4.0im] + du = rhs_oop(u, p_param, 0.0) + Test.@test du โ‰ˆ [1.0+2.0im, -3.0-4.0im] atol=1e-10 + end + + Test.@testset "build_rhs - complex matrix" begin + x0 = [1.0+2.0im 5.0+6.0im] + p0 = [3.0+4.0im 7.0+8.0im] + rhs = Systems.build_rhs(sys, x0, p0) + # x = [1+2im 5+6im], p = [3+4im 7+8im] + u = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + du = zeros(ComplexF64, 2, 2) + rhs(du, u, p_param, 0.0) + # dx = x = rows 1, dp = -p = rows 2 negated + Test.@test du โ‰ˆ [1.0+2.0im 5.0+6.0im; -3.0-4.0im -7.0-8.0im] atol=1e-10 + end + + Test.@testset "build_oop_rhs - complex matrix" begin + x0 = [1.0+2.0im 5.0+6.0im] + p0 = [3.0+4.0im 7.0+8.0im] + rhs_oop = Systems.build_oop_rhs(sys, x0, p0) + u = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + du = rhs_oop(u, p_param, 0.0) + Test.@test du โ‰ˆ [1.0+2.0im 5.0+6.0im; -3.0-4.0im -7.0-8.0im] atol=1e-10 + end + end + + # ==================================================================== + # UNIT TESTS - SVector unit + # ==================================================================== + + Test.@testset "SVector unit" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + + # Call hvf with SVector + x = SA[1.0, 2.0] + p = SA[3.0, 4.0] + dx, dp = hvf(x, p) + Test.@test dx == SA[1.0, 2.0] + Test.@test dp == SA[-3.0, -4.0] + + # Call hvf with complex SVector + x_c = SA[1.0+2.0im, 3.0+4.0im] + p_c = SA[5.0+6.0im, 7.0+8.0im] + dx_c, dp_c = hvf(x_c, p_c) + Test.@test dx_c == SA[1.0+2.0im, 3.0+4.0im] + Test.@test dp_c == SA[-5.0-6.0im, -7.0-8.0im] + + # Call build_oop_rhs with SVector (lazy builder) + sys = Systems.HamiltonianVectorFieldSystem(hvf) + x0 = SA[1.0, 2.0] + p0 = SA[3.0, 4.0] + rhs_oop = Systems.build_oop_rhs(sys, x0, p0) + u = SA[1.0, 2.0, 3.0, 4.0] + p_param = Common.ODEParameters(nothing) + du = rhs_oop(u, p_param, 0.0) + Test.@test du == SA[1.0, 2.0, -3.0, -4.0] + Test.@test du isa StaticArrays.SVector + end + + # ==================================================================== + # UNIT TESTS - SMatrix / SVector _ham_split (only Int dispatch) + # ==================================================================== + + Test.@testset "Static _ham_split" begin + # u = [X; P] with X = rows 1-2, P = rows 3-4 (column-major SMatrix{4,2}) + u_mat = SA[1.0 5.0; 2.0 6.0; 3.0 7.0; 4.0 8.0] + u_vec = SA[1.0, 2.0, 3.0, 4.0] + + Test.@testset "SVector + N known" begin + x, p = Systems._ham_split(u_vec, 2) + Test.@test x == SA[1.0, 2.0] + Test.@test p == SA[3.0, 4.0] + Test.@test x isa StaticArrays.SVector + Test.@test p isa StaticArrays.SVector + end + + Test.@testset "SMatrix + N known" begin + X, P = Systems._ham_split(u_mat, 2) + Test.@test X == SA[1.0 5.0; 2.0 6.0] + Test.@test P == SA[3.0 7.0; 4.0 8.0] + Test.@test X isa StaticArrays.SMatrix + Test.@test P isa StaticArrays.SMatrix + end + end + + # ==================================================================== + # UNIT TESTS - _ham_split + # ==================================================================== + + Test.@testset "_ham_split" begin + # Vector + N known + u_vec = [1.0, 2.0, 3.0, 4.0] + x, pk = Systems._ham_split(u_vec, 2) + Test.@test x == @view(u_vec[1:2]) + Test.@test pk == @view(u_vec[3:4]) + + # Matrix + N known + u_mat = [1.0 5.0; 2.0 6.0; 3.0 7.0; 4.0 8.0] + x3, pk3 = Systems._ham_split(u_mat, 2) + Test.@test x3 == @view(u_mat[1:2, :]) + Test.@test pk3 == @view(u_mat[3:4, :]) + end + + # ==================================================================== + # UNIT TESTS - _ham_assign! + # ==================================================================== + + Test.@testset "_ham_assign!" begin + # Vector + N known + du_vec = zeros(4) + dx = [1.0, 2.0] + dp = [-3.0, -4.0] + Systems._ham_assign!(du_vec, dx, dp, 2) + Test.@test du_vec == [1.0, 2.0, -3.0, -4.0] + + # Matrix + N known + du_mat = zeros(4, 2) + dx_mat = [1.0 5.0; 2.0 6.0] + dp_mat = [-3.0 -7.0; -4.0 -8.0] + Systems._ham_assign!(du_mat, dx_mat, dp_mat, 2) + Test.@test du_mat == [1.0 5.0; 2.0 6.0; -3.0 -7.0; -4.0 -8.0] + end + + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_hamiltonian_vector_field_system() = TestHamiltonianVectorFieldSystem.test_hamiltonian_vector_field_system() diff --git a/test/suite/systems/test_systems_module.jl b/test/suite/systems/test_systems_module.jl new file mode 100644 index 00000000..f6c8ff2a --- /dev/null +++ b/test/suite/systems/test_systems_module.jl @@ -0,0 +1,173 @@ +""" +# ============================================================================ +# Systems Module Exports Tests +# ============================================================================ +# This file tests the exports from the `Systems` module. It verifies that +# the expected types, functions, and constructors are properly exported by +# `CTFlows.Systems` and readily accessible to the end user. +""" + +module TestSystemsModule + +import Test +import CTFlows.Systems +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestSystemsModule + +function test_systems_module() + Test.@testset "Systems Module Exports" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + Test.@testset "AbstractSystem is exported" begin + Test.@test isdefined(Systems, :AbstractSystem) + Test.@test isabstracttype(Systems.AbstractSystem) + end + end + + # ==================================================================== + # Concrete Types + # ==================================================================== + + Test.@testset "Concrete Types" begin + Test.@testset "VectorFieldSystem is exported" begin + Test.@test isdefined(Systems, :VectorFieldSystem) + Test.@test Systems.VectorFieldSystem <: Systems.AbstractSystem + end + + Test.@testset "VectorFieldSystem constructor is exported" begin + Test.@test isdefined(Systems, :VectorFieldSystem) + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys isa Systems.VectorFieldSystem + Test.@test sys isa Systems.AbstractSystem + end + end + + # ==================================================================== + # Functions + # ==================================================================== + + Test.@testset "Functions" begin + Test.@testset "rhs is exported" begin + Test.@test isdefined(Systems, :rhs) + end + + Test.@testset "rhs returns a callable function" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs = Systems.rhs(sys) + Test.@test isa(rhs, Function) + + du = zeros(2) + u = [1.0, 2.0] + p = Common.ODEParameters(nothing) + rhs(du, u, p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + end + end + + # ==================================================================== + # Trait Support + # ==================================================================== + + Test.@testset "Trait Support" begin + Test.@testset "VectorFieldSystem has time dependence trait" begin + vf = Data.VectorField(x -> x) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.has_time_dependence_trait(sys) + end + + Test.@testset "VectorFieldSystem has variable dependence trait" begin + vf = Data.VectorField(x -> x) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.has_variable_dependence_trait(sys) + end + + Test.@testset "time_dependence function works with VectorFieldSystem" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true) + sys_aut = Systems.VectorFieldSystem(vf_aut) + Test.@test Traits.time_dependence(sys_aut) === Traits.Autonomous + + vf_non = Data.VectorField((t, x) -> x; is_autonomous=false) + sys_non = Systems.VectorFieldSystem(vf_non) + Test.@test Traits.time_dependence(sys_non) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence function works with VectorFieldSystem" begin + vf_fixed = Data.VectorField(x -> x; is_variable=false) + sys_fixed = Systems.VectorFieldSystem(vf_fixed) + Test.@test Traits.variable_dependence(sys_fixed) === Traits.Fixed + + vf_nonfixed = Data.VectorField((x, v) -> x .* v; is_variable=true) + sys_nonfixed = Systems.VectorFieldSystem(vf_nonfixed) + Test.@test Traits.variable_dependence(sys_nonfixed) === Traits.NonFixed + end + end + + # ==================================================================== + # Trait Propagation + # ==================================================================== + + Test.@testset "Trait Propagation" begin + Test.@testset "Autonomous Fixed traits propagate" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + end + + Test.@testset "NonAutonomous Fixed traits propagate" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + end + + Test.@testset "Autonomous NonFixed traits propagate" begin + vf = Data.VectorField((x, v) -> x .* v; is_autonomous=true, is_variable=true) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.variable_dependence(sys) === Traits.NonFixed + end + + Test.@testset "NonAutonomous NonFixed traits propagate" begin + vf = Data.VectorField((t, x, v) -> t .* x .* v; is_autonomous=false, is_variable=true) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(sys) === Traits.NonFixed + end + end + + # ==================================================================== + # Type Hierarchy Verification + # ==================================================================== + + Test.@testset "Type Hierarchy" begin + Test.@testset "VectorFieldSystem is a subtype of AbstractSystem" begin + Test.@test Systems.VectorFieldSystem <: Systems.AbstractSystem + end + + Test.@testset "Concrete VectorFieldSystem instances are AbstractSystem" begin + vf = Data.VectorField(x -> x) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys isa Systems.AbstractSystem + Test.@test sys isa Systems.VectorFieldSystem + end + end + end +end + +end # module TestSystemsModule + +# CRITICAL: Redefine in outer scope for TestRunner +test_systems_module() = TestSystemsModule.test_systems_module() diff --git a/test/suite/systems/test_vector_field_system.jl b/test/suite/systems/test_vector_field_system.jl new file mode 100644 index 00000000..167b214d --- /dev/null +++ b/test/suite/systems/test_vector_field_system.jl @@ -0,0 +1,439 @@ +module TestVectorFieldSystem + +import Test +import CTFlows.Systems +import CTFlows.Data +import CTFlows.Common +import CTFlows.Traits +import StaticArrays: SA, StaticArrays + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Test function +# ============================================================================== + +function test_vector_field_system() + Test.@testset "VectorFieldSystem Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys isa Systems.VectorFieldSystem + Test.@test sys isa Systems.AbstractSystem + end + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + Test.@testset "constructs from VectorField" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys isa Systems.VectorFieldSystem + Test.@test sys isa Systems.AbstractSystem + end + + Test.@testset "trait propagation - Autonomous Fixed" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + end + + Test.@testset "trait propagation - NonAutonomous Fixed" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(sys) === Traits.Fixed + end + + Test.@testset "trait propagation - Autonomous NonFixed" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.Autonomous + Test.@test Traits.variable_dependence(sys) === Traits.NonFixed + end + + Test.@testset "trait propagation - NonAutonomous NonFixed" begin + vf = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.time_dependence(sys) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(sys) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Contract Implementation + # ==================================================================== + + Test.@testset "Contract Implementation" begin + Test.@testset "rhs returns callable" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs = Systems.rhs(sys) + Test.@test rhs isa Function + end + + Test.@testset "rhs function has correct signature (du, u, p, t)" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs = Systems.rhs(sys) + du = zeros(2) + u = [1.0, 2.0] + p = Common.ODEParameters(nothing) + t = 0.0 + # Should not throw - signature is correct + rhs(du, u, p, t) + Test.@test du โ‰ˆ [-1.0, -2.0] atol=1e-10 + end + + Test.@testset "rhs function fills du in place" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs = Systems.rhs(sys) + du = zeros(2) + p = Common.ODEParameters(nothing) + rhs(du, [1.0, 2.0], p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] atol=1e-10 + end + + Test.@testset "rhs function uses underlying VectorField" begin + vf1 = Data.VectorField(x -> 2 .* x; is_autonomous=true, is_variable=false) + vf2 = Data.VectorField(x -> 3 .* x; is_autonomous=true, is_variable=false) + sys1 = Systems.VectorFieldSystem(vf1) + sys2 = Systems.VectorFieldSystem(vf2) + rhs1 = Systems.rhs(sys1) + rhs2 = Systems.rhs(sys2) + du1 = zeros(2) + du2 = zeros(2) + p = Common.ODEParameters(nothing) + rhs1(du1, [1.0, 1.0], p, 0.0) + rhs2(du2, [1.0, 1.0], p, 0.0) + Test.@test du1 โ‰ˆ [2.0, 2.0] atol=1e-10 + Test.@test du2 โ‰ˆ [3.0, 3.0] atol=1e-10 + end + + Test.@testset "rhs_oop returns callable" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs_oop = Systems.rhs_oop(sys) + Test.@test rhs_oop isa Function + end + + Test.@testset "rhs_oop function has correct signature (u, p, t)" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs_oop = Systems.rhs_oop(sys) + u = [1.0, 2.0] + p = Common.ODEParameters(nothing) + t = 0.0 + du = rhs_oop(u, p, t) + Test.@test du โ‰ˆ [-1.0, -2.0] atol=1e-10 + end + + Test.@testset "rhs_oop stored" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys.rhs_oop isa Function + Test.@test Systems.rhs_oop(sys) === sys.rhs_oop + end + + Test.@testset "rhs with matrix" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs = Systems.rhs(sys) + du = zeros(2, 3) + u = [1.0 2.0 3.0; 4.0 5.0 6.0] + p = Common.ODEParameters(nothing) + rhs(du, u, p, 0.0) + Test.@test du โ‰ˆ -u atol=1e-10 + end + + Test.@testset "rhs_oop with matrix" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs_oop = Systems.rhs_oop(sys) + u = [1.0 2.0 3.0; 4.0 5.0 6.0] + p = Common.ODEParameters(nothing) + du = rhs_oop(u, p, 0.0) + Test.@test du โ‰ˆ -u atol=1e-10 + end + end + + # ==================================================================== + # UNIT TESTS - Complex numbers + # ==================================================================== + + Test.@testset "Complex numbers" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs = Systems.rhs(sys) + rhs_oop = Systems.rhs_oop(sys) + p = Common.ODEParameters(nothing) + + Test.@testset "rhs - complex vector" begin + u = [1.0 + 2.0im, 3.0 + 4.0im] + du = zeros(ComplexF64, 2) + rhs(du, u, p, 0.0) + Test.@test du โ‰ˆ [-1.0-2.0im, -3.0-4.0im] atol=1e-10 + end + + Test.@testset "rhs_oop - complex vector" begin + u = [1.0 + 2.0im, 3.0 + 4.0im] + du = rhs_oop(u, p, 0.0) + Test.@test du โ‰ˆ [-1.0-2.0im, -3.0-4.0im] atol=1e-10 + end + + Test.@testset "rhs - complex matrix" begin + u = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + du = zeros(ComplexF64, 2, 2) + rhs(du, u, p, 0.0) + Test.@test du โ‰ˆ -u atol=1e-10 + end + + Test.@testset "rhs_oop - complex matrix" begin + u = [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im] + du = rhs_oop(u, p, 0.0) + Test.@test du โ‰ˆ -u atol=1e-10 + end + end + + # ==================================================================== + # UNIT TESTS - SVector unit + # ==================================================================== + + Test.@testset "SVector unit" begin + Test.@testset "rhs_oop with SVector" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs_oop = Systems.rhs_oop(sys) + u = SA[1.0, 2.0] + p = Common.ODEParameters(nothing) + du = rhs_oop(u, p, 0.0) + Test.@test du == SA[-1.0, -2.0] + end + + Test.@testset "rhs_oop with SVector complex" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + rhs_oop = Systems.rhs_oop(sys) + u = SA[1.0+2.0im, 3.0+4.0im] + p = Common.ODEParameters(nothing) + du = rhs_oop(u, p, 0.0) + Test.@test du == SA[-1.0-2.0im, -3.0-4.0im] + end + end + + # ==================================================================== + # UNIT TESTS - InPlace VectorField + # ==================================================================== + + Test.@testset "InPlace VectorField" begin + Test.@testset "OOP: rhs_oop_finalize is Nothing" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys.rhs_oop_finalize === nothing + end + + Test.@testset "IP: rhs_oop_finalize is Function" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test sys.rhs_oop_finalize isa Function + end + + Test.@testset "IP: rhs fills du via in-place call" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + du = zeros(2) + p = Common.ODEParameters(nothing) + Systems.rhs(sys)(du, [1.0, 2.0], p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + end + + Test.@testset "IP: rhs_oop(sys, true) === sys.rhs_oop" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Systems.rhs_oop(sys, true) === sys.rhs_oop + end + + Test.@testset "IP: rhs_oop(sys, false) === sys.rhs_oop_finalize and warns" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + f = Test.@test_logs (:warn, r"InPlace VectorField") Systems.rhs_oop(sys, false) + Test.@test f === sys.rhs_oop_finalize + end + + Test.@testset "IP: rhs_oop(sys, true) returns mutable Vector" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + f = Systems.rhs_oop(sys, true) + p = Common.ODEParameters(nothing) + du = f([1.0, 2.0], p, 0.0) + Test.@test du โ‰ˆ [-1.0, -2.0] + Test.@test du isa Vector + end + + Test.@testset "IP: rhs_oop_finalize returns SVector for SVector u" begin + vf = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + f = sys.rhs_oop_finalize + u = SA[1.0, 2.0] + p = Common.ODEParameters(nothing) + du = f(u, p, 0.0) + Test.@test du โ‰ˆ SA[-1.0, -2.0] + Test.@test du isa StaticArrays.SVector + end + + Test.@testset "OOP: rhs_oop(sys, false) still returns sys.rhs_oop" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Systems.rhs_oop(sys, true) === sys.rhs_oop + Test.@test Systems.rhs_oop(sys, false) === sys.rhs_oop + end + end + + # ==================================================================== + # UNIT TESTS - Trait Methods + # ==================================================================== + + Test.@testset "Trait Methods" begin + Test.@testset "time_dependence returns correct trait" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys_aut = Systems.VectorFieldSystem(vf_aut) + Test.@test Traits.time_dependence(sys_aut) === Traits.Autonomous + + vf_nonaut = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + sys_nonaut = Systems.VectorFieldSystem(vf_nonaut) + Test.@test Traits.time_dependence(sys_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys_fixed = Systems.VectorFieldSystem(vf_fixed) + Test.@test Traits.variable_dependence(sys_fixed) === Traits.Fixed + + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + sys_nonfixed = Systems.VectorFieldSystem(vf_nonfixed) + Test.@test Traits.variable_dependence(sys_nonfixed) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Show Methods + # ==================================================================== + + Test.@testset "Show Methods" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, sys) + str = String(take!(io)) + Test.@test occursin("VectorFieldSystem", str) + Test.@test occursin("wraps:", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + Test.@test occursin("out-of-place", str) + end + + Test.@testset "Base.show (text/plain)" begin + io = IOBuffer() + show(io, MIME("text/plain"), sys) + str = String(take!(io)) + Test.@test occursin("VectorFieldSystem", str) + Test.@test occursin("wraps:", str) + end + end + + # ==================================================================== + # UNIT TESTS - Common Trait Predicates + # ==================================================================== + + Test.@testset "Common Trait Predicates" begin + Test.@testset "has_time_dependence_trait returns true" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.has_time_dependence_trait(sys) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + Test.@test Traits.has_variable_dependence_trait(sys) === true + end + + Test.@testset "is_autonomous / is_nonautonomous" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys_aut = Systems.VectorFieldSystem(vf_aut) + Test.@test Traits.is_autonomous(sys_aut) === true + Test.@test Traits.is_nonautonomous(sys_aut) === false + + vf_nonaut = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + sys_nonaut = Systems.VectorFieldSystem(vf_nonaut) + Test.@test Traits.is_autonomous(sys_nonaut) === false + Test.@test Traits.is_nonautonomous(sys_nonaut) === true + end + + Test.@testset "is_variable / is_nonvariable" begin + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + sys_fixed = Systems.VectorFieldSystem(vf_fixed) + Test.@test Traits.is_variable(sys_fixed) === false + Test.@test Traits.is_nonvariable(sys_fixed) === true + + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + sys_nonfixed = Systems.VectorFieldSystem(vf_nonfixed) + Test.@test Traits.is_variable(sys_nonfixed) === true + Test.@test Traits.is_nonvariable(sys_nonfixed) === false + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Systems, :VectorFieldSystem) + end + end + + # ==================================================================== + # UNIT TESTS - Scalar InPlace Guard + # ==================================================================== + + Test.@testset "Scalar InPlace Guard" begin + Test.@testset "InPlace VF + scalar u0 throws ArgumentError" begin + vf = Data.VectorField((du, u) -> du .= -u; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = 1.0 # scalar + Test.@test_throws ArgumentError Systems._check_vf_scalar_inplace(sys, u0) + end + + Test.@testset "InPlace VF + vector u0 passes" begin + vf = Data.VectorField((du, u) -> du .= -u; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = [1.0, 2.0] # vector + Test.@test Systems._check_vf_scalar_inplace(sys, u0) === nothing + end + + Test.@testset "OutOfPlace VF + scalar u0 passes" begin + vf = Data.VectorField(u -> -u; is_autonomous=true, is_variable=false) + sys = Systems.VectorFieldSystem(vf) + u0 = 1.0 # scalar + Test.@test Systems._check_vf_scalar_inplace(sys, u0) === nothing + end + end + end +end + +end # module + +test_vector_field_system() = TestVectorFieldSystem.test_vector_field_system() diff --git a/test/suite/traits/test_abstract_traits.jl b/test/suite/traits/test_abstract_traits.jl new file mode 100644 index 00000000..a4a3f521 --- /dev/null +++ b/test/suite/traits/test_abstract_traits.jl @@ -0,0 +1,42 @@ +module TestAbstractTraits + +import Test +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_abstract_traits() + Test.@testset "Abstract Traits Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractTrait" begin + Test.@testset "AbstractTrait is exported" begin + Test.@test isdefined(Traits, :AbstractTrait) + end + + Test.@testset "AbstractTrait is abstract" begin + Test.@test isabstracttype(Traits.AbstractTrait) + end + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported abstract trait" begin + Test.@test isdefined(Traits, :AbstractTrait) + end + end + end +end + +end # module + +test_abstract_traits() = TestAbstractTraits.test_abstract_traits() diff --git a/test/suite/traits/test_ad.jl b/test/suite/traits/test_ad.jl new file mode 100644 index 00000000..aa408b0e --- /dev/null +++ b/test/suite/traits/test_ad.jl @@ -0,0 +1,119 @@ +module TestAD + +import Test +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_ad() + Test.@testset "AD Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractADTrait" begin + Test.@testset "AbstractADTrait is exported" begin + Test.@test isdefined(Traits, :AbstractADTrait) + end + + Test.@testset "AbstractADTrait is abstract" begin + Test.@test isabstracttype(Traits.AbstractADTrait) + end + + Test.@testset "AbstractADTrait subtypes AbstractTrait" begin + Test.@test Traits.AbstractADTrait <: Traits.AbstractTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Concrete Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Trait Types" begin + Test.@testset "WithAD" begin + Test.@testset "WithAD is exported" begin + Test.@test isdefined(Traits, :WithAD) + end + + Test.@testset "WithAD is concrete" begin + Test.@test !isabstracttype(Traits.WithAD) + end + + Test.@testset "WithAD instantiates" begin + with = Traits.WithAD() + Test.@test with isa Traits.WithAD + end + + Test.@testset "WithAD subtypes AbstractADTrait" begin + Test.@test Traits.WithAD <: Traits.AbstractADTrait + end + end + + Test.@testset "WithoutAD" begin + Test.@testset "WithoutAD is exported" begin + Test.@test isdefined(Traits, :WithoutAD) + end + + Test.@testset "WithoutAD is concrete" begin + Test.@test !isabstracttype(Traits.WithoutAD) + end + + Test.@testset "WithoutAD instantiates" begin + without = Traits.WithoutAD() + Test.@test without isa Traits.WithoutAD + end + + Test.@testset "WithoutAD subtypes AbstractADTrait" begin + Test.@test Traits.WithoutAD <: Traits.AbstractADTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Type Hierarchy + # ==================================================================== + + Test.@testset "UNIT TESTS - Type Hierarchy" begin + Test.@testset "All AD traits subtype AbstractTrait" begin + Test.@test Traits.WithAD <: Traits.AbstractTrait + Test.@test Traits.WithoutAD <: Traits.AbstractTrait + end + end + + # ==================================================================== + # UNIT TESTS - ad_trait function + # ==================================================================== + + Test.@testset "UNIT TESTS - ad_trait function" begin + Test.@testset "ad_trait default returns WithoutAD" begin + Test.@test Traits.ad_trait(42) === Traits.WithoutAD + Test.@test Traits.ad_trait("anything") === Traits.WithoutAD + Test.@test Traits.ad_trait(nothing) === Traits.WithoutAD + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported AD trait types" begin + for sym in (:AbstractADTrait, :WithAD, :WithoutAD) + Test.@test isdefined(Traits, sym) + end + end + + Test.@testset "Exported ad_trait function" begin + Test.@test isdefined(Traits, :ad_trait) + end + end + end +end + +end # module + +test_ad() = TestAD.test_ad() diff --git a/test/suite/traits/test_content.jl b/test/suite/traits/test_content.jl new file mode 100644 index 00000000..a1e07597 --- /dev/null +++ b/test/suite/traits/test_content.jl @@ -0,0 +1,129 @@ +module TestContent + +import Test +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_content() + Test.@testset "Content Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractContentTrait" begin + Test.@testset "AbstractContentTrait is exported" begin + Test.@test isdefined(Traits, :AbstractContentTrait) + end + + Test.@testset "AbstractContentTrait is abstract" begin + Test.@test isabstracttype(Traits.AbstractContentTrait) + end + + Test.@testset "AbstractContentTrait subtypes AbstractTrait" begin + Test.@test Traits.AbstractContentTrait <: Traits.AbstractTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Concrete Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Trait Types" begin + Test.@testset "StateTrait" begin + Test.@testset "StateTrait is exported" begin + Test.@test isdefined(Traits, :StateTrait) + end + + Test.@testset "StateTrait is concrete" begin + Test.@test !isabstracttype(Traits.StateTrait) + end + + Test.@testset "StateTrait instantiates" begin + st = Traits.StateTrait() + Test.@test st isa Traits.StateTrait + end + + Test.@testset "StateTrait subtypes AbstractContentTrait" begin + Test.@test Traits.StateTrait <: Traits.AbstractContentTrait + end + end + + Test.@testset "HamiltonianTrait" begin + Test.@testset "HamiltonianTrait is exported" begin + Test.@test isdefined(Traits, :HamiltonianTrait) + end + + Test.@testset "HamiltonianTrait is concrete" begin + Test.@test !isabstracttype(Traits.HamiltonianTrait) + end + + Test.@testset "HamiltonianTrait instantiates" begin + ham = Traits.HamiltonianTrait() + Test.@test ham isa Traits.HamiltonianTrait + end + + Test.@testset "HamiltonianTrait subtypes AbstractContentTrait" begin + Test.@test Traits.HamiltonianTrait <: Traits.AbstractContentTrait + end + end + + Test.@testset "AugmentedHamiltonianTrait" begin + Test.@testset "AugmentedHamiltonianTrait is exported" begin + Test.@test isdefined(Traits, :AugmentedHamiltonianTrait) + end + + Test.@testset "AugmentedHamiltonianTrait is concrete" begin + Test.@test !isabstracttype(Traits.AugmentedHamiltonianTrait) + end + + Test.@testset "AugmentedHamiltonianTrait instantiates" begin + aug = Traits.AugmentedHamiltonianTrait() + Test.@test aug isa Traits.AugmentedHamiltonianTrait + end + + Test.@testset "AugmentedHamiltonianTrait subtypes AbstractContentTrait" begin + Test.@test Traits.AugmentedHamiltonianTrait <: Traits.AbstractContentTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Type Hierarchy + # ==================================================================== + + Test.@testset "UNIT TESTS - Type Hierarchy" begin + Test.@testset "All content traits subtype AbstractTrait" begin + Test.@test Traits.StateTrait <: Traits.AbstractTrait + Test.@test Traits.HamiltonianTrait <: Traits.AbstractTrait + Test.@test Traits.AugmentedHamiltonianTrait <: Traits.AbstractTrait + end + + Test.@testset "Content traits are distinct from mode traits" begin + Test.@test !(Traits.StateTrait <: Traits.AbstractModeTrait) + Test.@test !(Traits.HamiltonianTrait <: Traits.AbstractModeTrait) + Test.@test !(Traits.AugmentedHamiltonianTrait <: Traits.AbstractModeTrait) + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported content trait types" begin + for sym in (:AbstractContentTrait, :StateTrait, :HamiltonianTrait, :AugmentedHamiltonianTrait) + Test.@test isdefined(Traits, sym) + end + end + end + end +end + +end # module + +test_content() = TestContent.test_content() diff --git a/test/suite/traits/test_mode.jl b/test/suite/traits/test_mode.jl new file mode 100644 index 00000000..8de56110 --- /dev/null +++ b/test/suite/traits/test_mode.jl @@ -0,0 +1,103 @@ +module TestMode + +import Test +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_mode() + Test.@testset "Mode Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractModeTrait" begin + Test.@testset "AbstractModeTrait is exported" begin + Test.@test isdefined(Traits, :AbstractModeTrait) + end + + Test.@testset "AbstractModeTrait is abstract" begin + Test.@test isabstracttype(Traits.AbstractModeTrait) + end + + Test.@testset "AbstractModeTrait subtypes AbstractTrait" begin + Test.@test Traits.AbstractModeTrait <: Traits.AbstractTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Concrete Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Trait Types" begin + Test.@testset "PointTrait" begin + Test.@testset "PointTrait is exported" begin + Test.@test isdefined(Traits, :PointTrait) + end + + Test.@testset "PointTrait is concrete" begin + Test.@test !isabstracttype(Traits.PointTrait) + end + + Test.@testset "PointTrait instantiates" begin + pt = Traits.PointTrait() + Test.@test pt isa Traits.PointTrait + end + + Test.@testset "PointTrait subtypes AbstractModeTrait" begin + Test.@test Traits.PointTrait <: Traits.AbstractModeTrait + end + end + + Test.@testset "TrajectoryTrait" begin + Test.@testset "TrajectoryTrait is exported" begin + Test.@test isdefined(Traits, :TrajectoryTrait) + end + + Test.@testset "TrajectoryTrait is concrete" begin + Test.@test !isabstracttype(Traits.TrajectoryTrait) + end + + Test.@testset "TrajectoryTrait instantiates" begin + traj = Traits.TrajectoryTrait() + Test.@test traj isa Traits.TrajectoryTrait + end + + Test.@testset "TrajectoryTrait subtypes AbstractModeTrait" begin + Test.@test Traits.TrajectoryTrait <: Traits.AbstractModeTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Type Hierarchy + # ==================================================================== + + Test.@testset "UNIT TESTS - Type Hierarchy" begin + Test.@testset "All mode traits subtype AbstractTrait" begin + Test.@test Traits.PointTrait <: Traits.AbstractTrait + Test.@test Traits.TrajectoryTrait <: Traits.AbstractTrait + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported mode trait types" begin + for sym in (:AbstractModeTrait, :PointTrait, :TrajectoryTrait) + Test.@test isdefined(Traits, sym) + end + end + end + end +end + +end # module + +test_mode() = TestMode.test_mode() diff --git a/test/suite/traits/test_mutability.jl b/test/suite/traits/test_mutability.jl new file mode 100644 index 00000000..0c3c727e --- /dev/null +++ b/test/suite/traits/test_mutability.jl @@ -0,0 +1,179 @@ +module TestMutability + +import Test +import CTBase.Exceptions +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake type for testing mutability trait pattern with InPlace. +""" +struct FakeInPlace end + +Traits.has_mutability_trait(::FakeInPlace) = true +Traits.mutability_trait(::FakeInPlace) = Traits.InPlace + +""" +Fake type for testing mutability trait pattern with OutOfPlace. +""" +struct FakeOutOfPlace end + +Traits.has_mutability_trait(::FakeOutOfPlace) = true +Traits.mutability_trait(::FakeOutOfPlace) = Traits.OutOfPlace + +""" +Fake type for testing mutability trait without the trait. +""" +struct FakeNoMutability end + +function test_mutability() + Test.@testset "Mutability Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractMutabilityTrait" begin + Test.@testset "AbstractMutabilityTrait is exported" begin + Test.@test isdefined(Traits, :AbstractMutabilityTrait) + end + + Test.@testset "AbstractMutabilityTrait is abstract" begin + Test.@test isabstracttype(Traits.AbstractMutabilityTrait) + end + + Test.@testset "AbstractMutabilityTrait subtypes AbstractTrait" begin + Test.@test Traits.AbstractMutabilityTrait <: Traits.AbstractTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Concrete Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Trait Types" begin + Test.@testset "InPlace" begin + Test.@testset "InPlace is exported" begin + Test.@test isdefined(Traits, :InPlace) + end + + Test.@testset "InPlace is concrete" begin + Test.@test !isabstracttype(Traits.InPlace) + end + + Test.@testset "InPlace instantiates" begin + ip = Traits.InPlace() + Test.@test ip isa Traits.InPlace + end + + Test.@testset "InPlace subtypes AbstractMutabilityTrait" begin + Test.@test Traits.InPlace <: Traits.AbstractMutabilityTrait + end + end + + Test.@testset "OutOfPlace" begin + Test.@testset "OutOfPlace is exported" begin + Test.@test isdefined(Traits, :OutOfPlace) + end + + Test.@testset "OutOfPlace is concrete" begin + Test.@test !isabstracttype(Traits.OutOfPlace) + end + + Test.@testset "OutOfPlace instantiates" begin + oop = Traits.OutOfPlace() + Test.@test oop isa Traits.OutOfPlace + end + + Test.@testset "OutOfPlace subtypes AbstractMutabilityTrait" begin + Test.@test Traits.OutOfPlace <: Traits.AbstractMutabilityTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Type Hierarchy + # ==================================================================== + + Test.@testset "UNIT TESTS - Type Hierarchy" begin + Test.@testset "All mutability traits subtype AbstractTrait" begin + Test.@test Traits.InPlace <: Traits.AbstractTrait + Test.@test Traits.OutOfPlace <: Traits.AbstractTrait + end + end + + # ==================================================================== + # ERROR TESTS - Fallback Methods + # ==================================================================== + + Test.@testset "ERROR TESTS - Fallback Methods" begin + Test.@testset "has_mutability_trait throws IncorrectArgument" begin + obj = "not a trait object" + Test.@test_throws Exceptions.IncorrectArgument Traits.has_mutability_trait(obj) + end + + Test.@testset "mutability_trait throws IncorrectArgument" begin + obj = "not a trait object" + Test.@test_throws Exceptions.IncorrectArgument Traits.mutability_trait(obj) + end + end + + # ==================================================================== + # CONTRACT TESTS - Mutability Trait Pattern + # ==================================================================== + + Test.@testset "CONTRACT TESTS - Mutability Trait Pattern" begin + Test.@testset "FakeInPlace trait implementation" begin + obj = FakeInPlace() + Test.@test Traits.has_mutability_trait(obj) === true + Test.@test Traits.mutability_trait(obj) === Traits.InPlace + Test.@test Traits.is_inplace(obj) === true + Test.@test Traits.is_outofplace(obj) === false + end + + Test.@testset "FakeOutOfPlace trait implementation" begin + obj = FakeOutOfPlace() + Test.@test Traits.has_mutability_trait(obj) === true + Test.@test Traits.mutability_trait(obj) === Traits.OutOfPlace + Test.@test Traits.is_inplace(obj) === false + Test.@test Traits.is_outofplace(obj) === true + end + + Test.@testset "FakeNoMutability throws errors" begin + obj = FakeNoMutability() + Test.@test_throws Exceptions.IncorrectArgument Traits.is_inplace(obj) + Test.@test_throws Exceptions.IncorrectArgument Traits.mutability_trait(obj) + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported mutability trait types" begin + for sym in (:AbstractMutabilityTrait, :InPlace, :OutOfPlace) + Test.@test isdefined(Traits, sym) + end + end + + Test.@testset "Exported mutability trait functions" begin + for sym in (:has_mutability_trait, :mutability_trait, :is_inplace, :is_outofplace) + Test.@test isdefined(Traits, sym) + end + end + end + end +end + +end # module + +test_mutability() = TestMutability.test_mutability() diff --git a/test/suite/traits/test_time_dependence.jl b/test/suite/traits/test_time_dependence.jl new file mode 100644 index 00000000..edd8cef5 --- /dev/null +++ b/test/suite/traits/test_time_dependence.jl @@ -0,0 +1,101 @@ +module TestTimeDependence + +import Test +import CTBase.Exceptions +import CTFlows.Traits +import CTModels.OCP + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake type for testing time-dependence trait pattern. +Implements both required methods: has_time_dependence_trait and time_dependence. +""" +struct FakeAutonomous end + +Traits.has_time_dependence_trait(::FakeAutonomous; kwargs...) = true +Traits.time_dependence(::FakeAutonomous) = OCP.Autonomous + +""" +Fake type for testing time-dependence trait pattern with NonAutonomous. +""" +struct FakeNonAutonomous end + +Traits.has_time_dependence_trait(::FakeNonAutonomous) = true +Traits.time_dependence(::FakeNonAutonomous) = OCP.NonAutonomous + +function test_time_dependence() + Test.@testset "Time Dependence Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Trait Types" begin + Test.@testset "TimeDependence abstract type" begin + Test.@test isdefined(OCP, :TimeDependence) + Test.@test OCP.Autonomous <: OCP.TimeDependence + Test.@test OCP.NonAutonomous <: OCP.TimeDependence + end + end + + # ==================================================================== + # ERROR TESTS - Fallback Methods + # ==================================================================== + + Test.@testset "ERROR TESTS - Fallback Methods" begin + Test.@testset "has_time_dependence_trait throws IncorrectArgument" begin + obj = "not a trait object" + Test.@test_throws Exceptions.IncorrectArgument Traits.has_time_dependence_trait(obj) + end + + Test.@testset "time_dependence throws IncorrectArgument" begin + obj = "not a trait object" + Test.@test_throws Exceptions.IncorrectArgument Traits.time_dependence(obj) + end + end + + # ==================================================================== + # CONTRACT TESTS - Time-Dependence Trait Pattern + # ==================================================================== + + Test.@testset "CONTRACT TESTS - Time-Dependence Trait Pattern" begin + Test.@testset "FakeAutonomous trait implementation" begin + obj = FakeAutonomous() + Test.@test Traits.has_time_dependence_trait(obj) === true + Test.@test Traits.time_dependence(obj) === OCP.Autonomous + Test.@test OCP.is_autonomous(obj) === true + Test.@test OCP.is_nonautonomous(obj) === false + end + + Test.@testset "FakeNonAutonomous trait implementation" begin + obj = FakeNonAutonomous() + Test.@test Traits.has_time_dependence_trait(obj) === true + Test.@test Traits.time_dependence(obj) === OCP.NonAutonomous + Test.@test OCP.is_autonomous(obj) === false + Test.@test OCP.is_nonautonomous(obj) === true + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported time-dependence trait functions" begin + for sym in (:has_time_dependence_trait, :time_dependence) + Test.@test isdefined(Traits, sym) + end + end + end + end +end + +end # module + +test_time_dependence() = TestTimeDependence.test_time_dependence() diff --git a/test/suite/traits/test_traits_module.jl b/test/suite/traits/test_traits_module.jl new file mode 100644 index 00000000..1f706421 --- /dev/null +++ b/test/suite/traits/test_traits_module.jl @@ -0,0 +1,62 @@ +module TestTraitsModule + +import Test +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_traits_module() + Test.@testset "Traits Module Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Module Structure + # ==================================================================== + + Test.@testset "UNIT TESTS - Module Structure" begin + Test.@testset "Traits module is defined" begin + Test.@test isdefined(Traits, :Traits) + end + + Test.@testset "Traits module has expected imports" begin + Test.@test isdefined(Traits, :Exceptions) + Test.@test isdefined(Traits, :OCP) + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported abstract types" begin + for sym in (:AbstractTrait, :AbstractModeTrait, :AbstractContentTrait, + :AbstractMutabilityTrait, :AbstractADTrait, + :AbstractVariableCostateCapability) + Test.@test isdefined(Traits, sym) + end + end + + Test.@testset "Exported concrete trait types" begin + for sym in (:PointTrait, :TrajectoryTrait, :StateTrait, :HamiltonianTrait, + :AugmentedHamiltonianTrait, :InPlace, :OutOfPlace, :WithAD, + :WithoutAD, :SupportsVariableCostate, :NoVariableCostate, + :VariableDependence, :Fixed, :NonFixed) + Test.@test isdefined(Traits, sym) + end + end + + Test.@testset "Exported trait functions" begin + for sym in (:ad_trait, :variable_costate_trait, :is_inplace, :is_outofplace, + :has_time_dependence_trait, :time_dependence, :has_mutability_trait, + :mutability_trait, :has_variable_dependence_trait, :variable_dependence) + Test.@test isdefined(Traits, sym) + end + end + end + end +end + +end # module + +test_traits_module() = TestTraitsModule.test_traits_module() diff --git a/test/suite/traits/test_variable_costate.jl b/test/suite/traits/test_variable_costate.jl new file mode 100644 index 00000000..af0d80e1 --- /dev/null +++ b/test/suite/traits/test_variable_costate.jl @@ -0,0 +1,124 @@ +module TestVariableCostate + +import Test +import CTFlows.Traits + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_variable_costate() + Test.@testset "Variable Costate Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Abstract Types" begin + Test.@testset "AbstractVariableCostateCapability" begin + Test.@testset "AbstractVariableCostateCapability is exported" begin + Test.@test isdefined(Traits, :AbstractVariableCostateCapability) + end + + Test.@testset "AbstractVariableCostateCapability is abstract" begin + Test.@test isabstracttype(Traits.AbstractVariableCostateCapability) + end + + Test.@testset "AbstractVariableCostateCapability subtypes AbstractTrait" begin + Test.@test Traits.AbstractVariableCostateCapability <: Traits.AbstractTrait + end + end + end + + # ==================================================================== + # UNIT TESTS - Concrete Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Concrete Trait Types" begin + Test.@testset "SupportsVariableCostate" begin + Test.@testset "SupportsVariableCostate is exported" begin + Test.@test isdefined(Traits, :SupportsVariableCostate) + end + + Test.@testset "SupportsVariableCostate is concrete" begin + Test.@test !isabstracttype(Traits.SupportsVariableCostate) + end + + Test.@testset "SupportsVariableCostate instantiates" begin + svc = Traits.SupportsVariableCostate() + Test.@test svc isa Traits.SupportsVariableCostate + end + + Test.@testset "SupportsVariableCostate subtypes AbstractVariableCostateCapability" begin + Test.@test Traits.SupportsVariableCostate <: Traits.AbstractVariableCostateCapability + end + end + + Test.@testset "NoVariableCostate" begin + Test.@testset "NoVariableCostate is exported" begin + Test.@test isdefined(Traits, :NoVariableCostate) + end + + Test.@testset "NoVariableCostate is concrete" begin + Test.@test !isabstracttype(Traits.NoVariableCostate) + end + + Test.@testset "NoVariableCostate instantiates" begin + nvc = Traits.NoVariableCostate() + Test.@test nvc isa Traits.NoVariableCostate + end + + Test.@testset "NoVariableCostate subtypes AbstractVariableCostateCapability" begin + Test.@test Traits.NoVariableCostate <: Traits.AbstractVariableCostateCapability + end + end + end + + # ==================================================================== + # UNIT TESTS - Type Hierarchy + # ==================================================================== + + Test.@testset "UNIT TESTS - Type Hierarchy" begin + Test.@testset "All variable costate traits subtype AbstractTrait" begin + Test.@test Traits.SupportsVariableCostate <: Traits.AbstractTrait + Test.@test Traits.NoVariableCostate <: Traits.AbstractTrait + end + + Test.@testset "Variable costate traits subtype AbstractVariableCostateCapability" begin + Test.@test Traits.SupportsVariableCostate <: Traits.AbstractVariableCostateCapability + Test.@test Traits.NoVariableCostate <: Traits.AbstractVariableCostateCapability + end + end + + # ==================================================================== + # UNIT TESTS - variable_costate_trait function + # ==================================================================== + + Test.@testset "UNIT TESTS - variable_costate_trait function" begin + Test.@testset "variable_costate_trait default returns NoVariableCostate" begin + Test.@test Traits.variable_costate_trait(42) === Traits.NoVariableCostate + Test.@test Traits.variable_costate_trait("anything") === Traits.NoVariableCostate + Test.@test Traits.variable_costate_trait(nothing) === Traits.NoVariableCostate + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported variable costate trait types" begin + for sym in (:AbstractVariableCostateCapability, :SupportsVariableCostate, :NoVariableCostate) + Test.@test isdefined(Traits, sym) + end + end + + Test.@testset "Exported variable_costate_trait function" begin + Test.@test isdefined(Traits, :variable_costate_trait) + end + end + end +end + +end # module + +test_variable_costate() = TestVariableCostate.test_variable_costate() diff --git a/test/suite/traits/test_variable_dependence.jl b/test/suite/traits/test_variable_dependence.jl new file mode 100644 index 00000000..daa9c68d --- /dev/null +++ b/test/suite/traits/test_variable_dependence.jl @@ -0,0 +1,114 @@ +module TestVariableDependence + +import Test +import CTBase.Exceptions +import CTFlows.Traits +import CTModels.OCP + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================== +# Fake types for contract testing +# ============================================================================== + +""" +Fake type for testing variable-dependence trait pattern. +Implements both required methods: has_variable_dependence_trait and variable_dependence. +""" +struct FakeFixed end + +Traits.has_variable_dependence_trait(::FakeFixed) = true +Traits.variable_dependence(::FakeFixed) = Traits.Fixed + +""" +Fake type for testing variable-dependence trait pattern with NonFixed. +""" +struct FakeNonFixed end + +Traits.has_variable_dependence_trait(::FakeNonFixed) = true +Traits.variable_dependence(::FakeNonFixed) = Traits.NonFixed + +function test_variable_dependence() + Test.@testset "Variable Dependence Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Trait Types + # ==================================================================== + + Test.@testset "UNIT TESTS - Trait Types" begin + Test.@testset "VariableDependence abstract type" begin + Test.@test isdefined(Traits, :VariableDependence) + Test.@test Traits.Fixed <: Traits.VariableDependence + Test.@test Traits.NonFixed <: Traits.VariableDependence + end + + Test.@testset "Concrete trait types" begin + Test.@test Traits.Fixed() isa Traits.Fixed + Test.@test Traits.NonFixed() isa Traits.NonFixed + end + end + + # ==================================================================== + # ERROR TESTS - Fallback Methods + # ==================================================================== + + Test.@testset "ERROR TESTS - Fallback Methods" begin + Test.@testset "has_variable_dependence_trait throws IncorrectArgument" begin + obj = "not a trait object" + Test.@test_throws Exceptions.IncorrectArgument Traits.has_variable_dependence_trait(obj) + end + + Test.@testset "variable_dependence throws IncorrectArgument" begin + obj = "not a trait object" + Test.@test_throws Exceptions.IncorrectArgument Traits.variable_dependence(obj) + end + end + + # ==================================================================== + # CONTRACT TESTS - Variable-Dependence Trait Pattern + # ==================================================================== + + Test.@testset "CONTRACT TESTS - Variable-Dependence Trait Pattern" begin + Test.@testset "FakeFixed trait implementation" begin + obj = FakeFixed() + Test.@test Traits.has_variable_dependence_trait(obj) === true + Test.@test Traits.variable_dependence(obj) === Traits.Fixed + Test.@test OCP.is_variable(obj) === false + Test.@test OCP.is_nonvariable(obj) === true + Test.@test OCP.has_variable(obj) === false + end + + Test.@testset "FakeNonFixed trait implementation" begin + obj = FakeNonFixed() + Test.@test Traits.has_variable_dependence_trait(obj) === true + Test.@test Traits.variable_dependence(obj) === Traits.NonFixed + Test.@test OCP.is_variable(obj) === true + Test.@test OCP.is_nonvariable(obj) === false + Test.@test OCP.has_variable(obj) === true + end + end + + # ==================================================================== + # Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported variable-dependence trait types" begin + for sym in (:VariableDependence, :Fixed, :NonFixed) + Test.@test isdefined(Traits, sym) + end + end + + Test.@testset "Exported variable-dependence trait functions" begin + for sym in (:has_variable_dependence_trait, :variable_dependence) + Test.@test isdefined(Traits, sym) + end + end + end + end +end + +end # module + +test_variable_dependence() = TestVariableDependence.test_variable_dependence() diff --git a/windsurf-rule-based/rules/architecture.md b/windsurf-rule-based/rules/architecture.md new file mode 100644 index 00000000..61238328 --- /dev/null +++ b/windsurf-rule-based/rules/architecture.md @@ -0,0 +1,678 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Architecture and Design Principles + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“‹ **Applying Architecture Rule**: [specific principle being applied]" + +This ensures transparency about which architectural principle is being used and why. + +--- + +This document defines architecture and design principles for Julia code. These principles ensure code is maintainable, extensible, and follows best practices. + +## Core Principles + +1. **SRP** โ€” Each module, function, and type has one clear purpose +2. **OCP** โ€” Open for extension, closed for modification +3. **LSP** โ€” Subtypes must honor parent contracts +4. **ISP** โ€” Keep interfaces small and focused +5. **DIP** โ€” Depend on abstractions, not concrete implementations +6. **DRY** โ€” No code duplication +7. **KISS** โ€” Prefer simple solutions +8. **YAGNI** โ€” Don't add functionality until actually needed + +--- + +## SOLID Principles in Julia + +### Single Responsibility Principle (SRP) + +Every module, function, and type should have a single, well-defined responsibility. + +**โœ… Good - Focused responsibilities:** + +```julia +# Parsing responsibility +function parse_ocp_input(text::String) + return parsed_data +end + +# Validation responsibility +function validate_ocp_data(data) + return is_valid, errors +end + +# Processing responsibility +function solve_ocp(data) + return solution +end +``` + +**โŒ Bad - Too many responsibilities:** + +```julia +function handle_ocp(text::String) + parsed = parse(text) # Parsing + validate(parsed) # Validation + solution = solve(parsed) # Processing + save_to_file(solution, "out") # I/O + return format_output(solution) # Formatting +end +``` + +**Red flags:** + +- Function names with "and" or "or" +- Functions longer than 50 lines +- Multiple `if-else` branches handling different concerns +- Modules mixing unrelated functionality + +--- + +### Open/Closed Principle (OCP) + +Software should be open for extension but closed for modification. + +**โœ… Good - Extensible via abstract types:** + +```julia +# Define abstract interface +abstract type AbstractOptimizationProblem end + +# Existing implementation +struct LinearProblem <: AbstractOptimizationProblem + A::Matrix + b::Vector +end + +# Solver works with any AbstractOptimizationProblem +function solve(problem::AbstractOptimizationProblem) + # Generic solving logic +end + +# NEW: Extend without modifying existing code +struct NonlinearProblem <: AbstractOptimizationProblem + f::Function + x0::Vector +end +# Solver automatically works via multiple dispatch +``` + +**โŒ Bad - Hard-coded type checks:** + +```julia +function solve(problem) + if problem isa LinearProblem + # Linear solving + elseif problem isa NonlinearProblem + # Nonlinear solving + # Need to modify for every new type! + end +end +``` + +**How to apply:** + +- Use abstract types to define interfaces +- Leverage multiple dispatch for extensibility +- Avoid type checking with `isa` or `typeof` +- Design type hierarchies that allow new subtypes + +--- + +### Liskov Substitution Principle (LSP) + +Subtypes must be substitutable for their parent types without breaking functionality. + +**โœ… Good - Consistent interface:** + +```julia +abstract type AbstractModel end + +# Contract: all models must implement `evaluate` +function evaluate(model::AbstractModel, x) + throw(NotImplemented("evaluate not implemented for $(typeof(model))")) +end + +# Subtype honors contract +struct LinearModel <: AbstractModel + coeffs::Vector +end + +function evaluate(model::LinearModel, x) + return dot(model.coeffs, x) # Returns a number +end + +# Generic code works with any AbstractModel +function optimize(model::AbstractModel, x0) + value = evaluate(model, x0) # Safe for any model + # ... +end +``` + +**โŒ Bad - Subtype breaks contract:** + +```julia +struct BrokenModel <: AbstractModel + data::String +end + +function evaluate(model::BrokenModel, x) + return "error: invalid" # Returns String, not number! +end + +# This breaks unexpectedly +function optimize(model::AbstractModel, x0) + value = evaluate(model, x0) + gradient = value * 2 # ERROR if value is String! +end +``` + +**How to apply:** + +- Define clear contracts for abstract types (via docstrings) +- Ensure all subtypes implement required methods consistently +- Return types should be compatible across hierarchy +- Test that generic code works with all subtypes + +**Testing LSP:** + +```julia +@testset "Liskov Substitution" begin + # Test that all subtypes work with generic code + for ModelType in [LinearModel, QuadraticModel, CustomModel] + model = ModelType(test_params...) + @test evaluate(model, x) isa Number + @test optimize(model, x0) isa Solution + end +end +``` + +--- + +### Interface Segregation Principle (ISP) + +Keep interfaces small and focused. Don't force clients to depend on methods they don't use. + +**โœ… Good - Small, focused interfaces:** + +```julia +# Separate capabilities +abstract type Evaluable end +abstract type Differentiable end + +# Types implement only what they need +struct SimpleFunction <: Evaluable + f::Function +end + +struct SmoothFunction <: Union{Evaluable, Differentiable} + f::Function + df::Function +end + +# Clients depend only on what they need +function plot_function(f::Evaluable, xs) + return [evaluate(f, x) for x in xs] +end + +function optimize(f::Differentiable, x0) + return gradient_descent(f, x0) +end +``` + +**โŒ Bad - Bloated interface:** + +```julia +# Forces all types to implement everything +abstract type MathFunction end + +# Required methods (even if not needed): +evaluate(f::MathFunction, x) = error("not implemented") +gradient(f::MathFunction, x) = error("not implemented") +hessian(f::MathFunction, x) = error("not implemented") +integrate(f::MathFunction, a, b) = error("not implemented") + +# Simple function forced to implement everything +struct SimpleFunction <: MathFunction + f::Function +end + +evaluate(sf::SimpleFunction, x) = sf.f(x) +gradient(sf::SimpleFunction, x) = error("not differentiable") # Forced! +hessian(sf::SimpleFunction, x) = error("not differentiable") # Forced! +integrate(sf::SimpleFunction, a, b) = error("not integrable") # Forced! +``` + +**How to apply:** + +- Create small, focused abstract types +- Use `Union` types for multiple interfaces +- Don't force implementations of unused methods +- Export only necessary functions + +--- + +### Dependency Inversion Principle (DIP) + +Depend on abstractions, not concrete implementations. + +**โœ… Good - Depend on abstractions:** + +```julia +# High-level abstraction +abstract type DataStore end + +# High-level module depends on abstraction +struct DataProcessor + store::DataStore # Abstract type +end + +function process(dp::DataProcessor, data) + save(dp.store, data) # Works with any DataStore +end + +# Low-level implementations +struct FileStore <: DataStore + path::String +end + +struct DatabaseStore <: DataStore + connection::DBConnection +end + +# Easy to swap implementations +processor1 = DataProcessor(FileStore("data.txt")) +processor2 = DataProcessor(DatabaseStore(conn)) +``` + +**โŒ Bad - Depend on concrete types:** + +```julia +# Tightly coupled to file system +struct DataProcessor + file_path::String +end + +function process(dp::DataProcessor, data) + write(dp.file_path, data) # Hard-coded to files +end + +# Can't switch to database without modifying DataProcessor +``` + +**How to apply:** + +- Define abstract types for dependencies +- Pass abstract types as arguments +- Use dependency injection +- Avoid hard-coding concrete types + +--- + +## Other Design Principles + +### DRY - Don't Repeat Yourself + +Avoid code duplication. Every piece of knowledge should have a single representation. + +**โœ… Good - Extract common logic:** + +```julia +function validate_positive(x, name) + x > 0 || throw(IncorrectArgument("$name must be positive")) +end + +function create_model(n::Int, m::Int) + validate_positive(n, "n") + validate_positive(m, "m") + return Model(n, m) +end +``` + +**โŒ Bad - Duplicated validation:** + +```julia +function create_model(n::Int, m::Int) + n > 0 || throw(ArgumentError("n must be positive")) + m > 0 || throw(ArgumentError("m must be positive")) + return Model(n, m) +end + +function create_problem(n::Int, m::Int) + n > 0 || throw(ArgumentError("n must be positive")) # Duplicated! + m > 0 || throw(ArgumentError("m must be positive")) # Duplicated! + return Problem(n, m) +end +``` + +--- + +### KISS - Keep It Simple, Stupid + +Prefer simple solutions over complex ones. Avoid over-engineering. + +**โœ… Good - Simple and clear:** + +```julia +function compute_mean(xs) + return sum(xs) / length(xs) +end +``` + +**โŒ Bad - Over-engineered:** + +```julia +function compute_mean(xs) + accumulator = zero(eltype(xs)) + counter = 0 + for x in xs + accumulator = accumulator + x + counter = counter + 1 + end + return accumulator / counter +end +``` + +--- + +### YAGNI - You Aren't Gonna Need It + +Don't add functionality until it's actually needed. + +**โœ… Good - Implement what's needed:** + +```julia +struct Model + coeffs::Vector{Float64} +end + +function evaluate(m::Model, x) + return dot(m.coeffs, x) +end +``` + +**โŒ Bad - Premature features:** + +```julia +struct Model + coeffs::Vector{Float64} + cache::Dict{Vector, Float64} # Not needed yet + optimization_history::Vector # Not needed yet + metadata::Dict{Symbol, Any} # Not needed yet + version::String # Not needed yet +end +``` + +--- + +## Julia-Specific Patterns + +### Multiple Dispatch + +Use multiple dispatch for extensibility and clarity: + +```julia +# Define behavior for different type combinations +function combine(a::Number, b::Number) + return a + b +end + +function combine(a::Vector, b::Vector) + return vcat(a, b) +end + +function combine(a::String, b::String) + return a * b +end + +# Extensible: add new methods without modifying existing code +``` + +--- + +### Type Hierarchies + +Design type hierarchies that reflect conceptual relationships: + +```julia +# Clear hierarchy +abstract type AbstractStrategy end +abstract type AbstractDirectMethod <: AbstractStrategy end +abstract type AbstractIndirectMethod <: AbstractStrategy end + +struct DirectShooting <: AbstractDirectMethod end +struct DirectCollocation <: AbstractDirectMethod end +struct IndirectShooting <: AbstractIndirectMethod end +``` + +--- + +### Composition Over Inheritance + +Prefer composition (has-a) over inheritance (is-a) when appropriate: + +```julia +# Composition: Model has a solver +struct OptimizationModel + problem::AbstractProblem + solver::AbstractSolver + options::NamedTuple +end + +# Not: OptimizationModel <: AbstractSolver +``` + +--- + +### Parametric Types + +Use parametric types for type stability and flexibility: + +```julia +# Type-stable with parameters +struct Container{T} + items::Vector{T} +end + +# Flexible: works with any type +c1 = Container([1, 2, 3]) # Container{Int} +c2 = Container([1.0, 2.0, 3.0]) # Container{Float64} +``` + +--- + +## Module Organization + +### Layered Architecture + +Organize code in layers with clear dependencies: + +```text +Low-level (Core types, utilities) + โ†“ +Mid-level (Business logic, algorithms) + โ†“ +High-level (User-facing API, orchestration) +``` + +**Example:** + +```julia +# Low-level: Core types +module Types + abstract type AbstractProblem end + struct Problem <: AbstractProblem + # ... + end +end + +# Mid-level: Algorithms +module Solvers + using ..Types + function solve(p::AbstractProblem) + # ... + end +end + +# High-level: User API +module API + using ..Types + using ..Solvers + export solve, Problem +end +``` + +--- + +### Separation of Concerns + +Keep different concerns in separate modules: + +```julia +# Validation logic +module Validation + function validate_dimensions(n, m) + # ... + end +end + +# Parsing logic +module Parsing + function parse_input(text) + # ... + end +end + +# Business logic +module Core + using ..Validation + using ..Parsing + # ... +end +``` + +--- + +## Quality Checklist + +Before finalizing code, verify: + +- [ ] Each function has a single, clear responsibility +- [ ] Abstract types define clear interfaces +- [ ] Subtypes honor parent contracts (LSP) +- [ ] No hard-coded type checks (`isa`, `typeof`) +- [ ] Dependencies are on abstractions, not concrete types +- [ ] No code duplication (DRY) +- [ ] Solution is as simple as possible (KISS) +- [ ] No premature features (YAGNI) +- [ ] Multiple dispatch used appropriately +- [ ] Type hierarchies reflect conceptual relationships +- [ ] Module organization follows layered architecture + +--- + +## Common Anti-Patterns + +### God Object + +**โŒ Avoid:** One object that does everything + +```julia +struct System + data::Dict + config::Dict + state::Dict + # 50+ fields +end + +# 100+ methods operating on System +``` + +**โœ… Instead:** Split into focused components + +```julia +struct DataManager + data::Dict +end + +struct ConfigManager + config::Dict +end + +struct StateManager + state::Dict +end +``` + +--- + +### Primitive Obsession + +**โŒ Avoid:** Using primitives instead of domain types + +```julia +function create_problem(n::Int, m::Int, t0::Float64, tf::Float64) + # What do these numbers mean? +end +``` + +**โœ… Instead:** Use domain types + +```julia +struct Dimensions + state::Int + control::Int +end + +struct TimeInterval + initial::Float64 + final::Float64 +end + +function create_problem(dims::Dimensions, time::TimeInterval) + # Clear meaning +end +``` + +--- + +### Feature Envy + +**โŒ Avoid:** Methods that use more of another type's data + +```julia +function compute_cost(model::Model, data::Data) + # Uses mostly data fields, not model fields + return data.a * data.b + data.c +end +``` + +**โœ… Instead:** Move method to appropriate type + +```julia +function compute_cost(data::Data) + return data.a * data.b + data.c +end +``` + +--- + +## References + +- [Julia Style Guide](https://docs.julialang.org/en/v1/manual/style-guide/) +- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) +- [Design Patterns in Julia](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md) + +--- + +## Related Rules + +- `.windsurf/rules/docstrings.md` - Documentation standards +- `.windsurf/rules/testing.md` - Testing standards +- `.windsurf/rules/type-stability.md` - Type stability standards diff --git a/windsurf-rule-based/rules/docstrings.md b/windsurf-rule-based/rules/docstrings.md new file mode 100644 index 00000000..afee6911 --- /dev/null +++ b/windsurf-rule-based/rules/docstrings.md @@ -0,0 +1,352 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Documentation Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“š **Applying Documentation Rule**: [specific documentation principle being applied]" + +This ensures transparency about which documentation standard is being used and why. + +--- + +This document defines the documentation standards for the Control Toolbox project. All Julia code (functions, structs, macros, modules) must be documented following these guidelines. + +## Core Principles + +1. **Completeness**: Every exported symbol and significant internal component must have a docstring +2. **Accuracy**: Documentation must reflect actual behavior, not aspirational or outdated information +3. **Clarity**: Write for users who understand Julia but may be unfamiliar with the specific domain +4. **Consistency**: Follow the templates and conventions defined here + +## Docstring Placement + +- Docstrings go **immediately above** the declaration they document +- No blank lines between docstring and declaration +- For multi-method functions, document the most general signature or provide method-specific docstrings + +## Required Docstring Structure + +Every docstring should contain: + +1. **Signature line** (for functions): Use `$(TYPEDSIGNATURES)` from DocStringExtensions +2. **One-sentence summary**: Clear, concise description of purpose +3. **Detailed description** (if needed): Explain behavior, constraints, invariants, edge cases +4. **Structured sections** (as applicable): + - `# Arguments`: For functions/macros + - `# Fields`: For structs/types + - `# Returns`: For functions that return values + - `# Throws`: For functions that may throw exceptions + - `# Example` or `# Examples`: Demonstrate usage + - `# Notes`: Performance considerations, stability warnings, implementation details + - `# References`: Citations to papers, algorithms, or external documentation + - `See also:`: Related functions/types with `[@ref]` links + +## Cross-References + +### Internal References + +For symbols within the current package or its dependencies, use `[@ref]` syntax with **full module path** including the root package and submodules: + +```julia +See also: [`CTFlows.Flows.build_flow`](@ref), [`CTFlows.Flows.AbstractFlow`](@ref) +``` + +**Rules for @ref:** + +1. Use full module path including root package (e.g., `CTFlows.Integrators.SciMLTag`, not just `SciMLTag`) +2. Include all nested submodules in the path +3. Only use for symbols documented in the current package's documentation + +**Examples:** + +โœ… **Correct internal references:** + +- [`CTFlows.Integrators.SciMLTag`](@ref) +- [`CTFlows.Flows.AbstractFlow`](@ref) +- [`CTFlows.Common.AbstractTag`](@ref) + +โŒ **Incorrect internal references:** + +- [`SciMLTag`](@ref) # Missing module qualification +- [`Integrators.SciMLTag`](@ref) # Missing root package name + +### External Package References + +For symbols in external packages that are not part of the current documentation build, use `[@extref]` syntax with the **full module path** including submodules: + +```julia +See also: [`CTSolvers.Options.OptionValue`](@extref) +``` + +**Rules for @extref:** + +1. Use the complete module path (e.g., `CTSolvers.Options.OptionValue`, not just `OptionValue`) +2. Include all submodules in the path +3. Only use for symbols that are not documented in the current package's documentation +4. Use when the symbol is from a dependency that has its own separate documentation + +**Examples:** + +โœ… **Correct external references:** + +- [`CTBase.Exceptions.IncorrectArgument`](@extref) +- [`CTSolvers.Options.OptionValue`](@extref) +- [`CTBase.Tags.Autonomous`](@extref) + +โŒ **Incorrect external references:** + +- [`OptionValue`](@extref) # Missing module path +- [`CTSolvers.OptionValue`](@ref) # Wrong syntax for external symbol + +**When to use which:** + +- Use `[@ref]` for symbols within CTFlows or its included documentation +- Use `[@extref]` for symbols from external packages with separate documentation (CTBase, CTSolvers) + +## CTFlows Submodule Qualification + +Public symbols are always accessed through their submodule, never through the root package: + +```julia +CTFlows.Flows.build_flow(...) # โœ… correct +CTFlows.build_flow(...) # โŒ wrong โ€” nothing exported at root level +``` + +### Exposed Submodules + +| Submodule | Exported symbols (examples) | +| --- | --- | +| `CTFlows.Common` | `AbstractTag`, `AbstractTrait`, `Autonomous`, `Fixed` | +| `CTFlows.Data` | `VectorField`, `HamiltonianVectorField` | +| `CTFlows.Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow` | +| `CTFlows.Integrators` | `AbstractIntegrator`, `AbstractIntegrationResult` | + +## Docstring Templates + +### Function Template + +```julia +""" +$(TYPEDSIGNATURES) + +One-sentence description of what the function does. + +Optional detailed explanation covering: +- Behavior and semantics +- Constraints and preconditions +- Common use cases or patterns + +# Arguments +- `arg1::Type1`: Description of first argument +- `arg2::Type2`: Description of second argument + +# Returns +- `ReturnType`: Description of return value + +# Throws +- `ExceptionType`: When and why this exception is thrown + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> result = function_name(arg1, arg2) +expected_output +\`\`\` + +# Notes +- Performance characteristics (if relevant) +- Thread safety (if relevant) +- Stability guarantees + +See also: [`CTFlows.Flows.related_function`](@ref), [`CTFlows.Flows.RelatedType`](@ref) +""" +function function_name(arg1::Type1, arg2::Type2)::ReturnType + # implementation +end +``` + +--- + +### Struct Template + +```julia +""" +$(TYPEDEF) + +One-sentence description of what this type represents. + +Optional detailed explanation covering: +- Purpose and design intent +- Invariants that must be maintained +- Relationship to other types + +# Fields +- `field1::Type1`: Description and constraints +- `field2::Type2`: Description and constraints + +# Constructor Validation + +Describe any validation performed by constructors (if applicable). + +# Example +\`\`\`julia-repl +julia> using CTFlows.Flows + +julia> obj = StructName(value1, value2) +StructName(...) + +julia> obj.field1 +value1 +\`\`\` + +# Notes +- Mutability status (if not obvious from declaration) +- Performance considerations + +See also: [`CTFlows.Flows.related_type`](@ref), [`CTFlows.Flows.constructor_function`](@ref) +""" +struct StructName{T} + field1::Type1 + field2::Type2 +end +``` + +--- + +### Abstract Type Template + +```julia +""" +$(TYPEDEF) + +One-sentence description of the abstraction. + +Detailed explanation of: +- What types should subtype this +- Contract/interface requirements for subtypes +- Common behavior across all subtypes + +# Interface Requirements + +List methods that subtypes must implement: +- `required_method(::SubType)`: Description + +# Example +\`\`\`julia-repl +julia> using CTFlows.Common + +julia> MyType <: AbstractTypeName +true +\`\`\` + +See also: [`CTFlows.Common.ConcreteSubtype1`](@ref), [`CTFlows.Common.ConcreteSubtype2`](@ref) +""" +abstract type AbstractTypeName end +``` + +--- + +## Example Safety Policy + +Examples in docstrings must be **safe and reproducible**: + +### โœ… Safe Examples + +- Pure computations with deterministic results +- Constructors with simple, valid inputs +- Queries on created objects +- Examples that start with `using CTFlows.Submodule` + +### โŒ Unsafe Examples + +- File system operations (reading/writing files) +- Network requests +- Database operations +- Git operations +- Non-deterministic behavior (random numbers without seed, timing-dependent code) +- Long-running computations (>1 second) +- Dependencies on external state or global variables + +### Fallback for Complex Cases + +If a safe, runnable example cannot be provided: + +- Use a plain code block (\`\`\`julia) instead of REPL block (\`\`\`julia-repl) +- Show usage patterns without claiming specific output +- Provide a conceptual sketch of how to use the API + +Example: + +```julia +# Example +\`\`\`julia +# Conceptual usage pattern +flow = CTFlows.Flows.build_flow(sys, integrator) +result = CTFlows.Flows.evaluate(flow, t0, tf) +\`\`\` +``` + +## Module Prefix Convention + +- **Exported symbols**: Use directly without module prefix + + ```julia-repl + julia> using CTFlows.Flows + julia> flow = build_flow(sys, integrator) # build_flow is exported + ``` + +- **Internal symbols**: Use module prefix + + ```julia-repl + julia> using CTFlows.Flows + julia> Flows.internal_function(...) # Not exported + ``` + +When a docstring is defined inside `src/Flows/flow.jl`, the public prefix shown in the docs is `CTFlows.Flows.`: + +```julia +""" +$(TYPEDSIGNATURES) + +Build a flow from a system and an integrator. + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Integrators.AbstractIntegrator`](@ref) +""" +function build_flow(sys::AbstractSystem, integrator::AbstractIntegrator) +``` + +## DocStringExtensions Macros + +This project uses [DocStringExtensions.jl](https://github.com/JuliaDocs/DocStringExtensions.jl): + +- `$(TYPEDEF)`: Auto-generates type signature for structs/abstract types +- `$(TYPEDSIGNATURES)`: Auto-generates function signature with types +- Use these instead of manually writing signatures + +## Quality Checklist + +Before finalizing a docstring, verify: + +- [ ] Docstring is directly above the declaration (no blank lines) +- [ ] Uses `$(TYPEDEF)` or `$(TYPEDSIGNATURES)` where applicable +- [ ] One-sentence summary is clear and accurate +- [ ] All arguments/fields are documented with types and descriptions +- [ ] Return value is documented (if applicable) +- [ ] Exceptions are documented (if thrown) +- [ ] Example is safe, runnable, and demonstrates typical usage +- [ ] Cross-references use `[@ref]` for internal symbols +- [ ] Cross-references use `[@extref]` for external symbols +- [ ] Full module paths are used (e.g., `CTFlows.Flows.build_flow`) +- [ ] No invented behavior or aspirational features +- [ ] Consistent with project style and terminology + +## Related Rules + +- `.windsurf/rules/architecture.md` - Architecture and design principles +- `.windsurf/rules/testing.md` - Testing standards +- `.windsurf/rules/type-stability.md` - Type stability standards diff --git a/windsurf-rule-based/rules/documentation.md b/windsurf-rule-based/rules/documentation.md new file mode 100644 index 00000000..0a52ab21 --- /dev/null +++ b/windsurf-rule-based/rules/documentation.md @@ -0,0 +1,464 @@ +--- +trigger: glob +glob: "docs/**/*" +--- + +# Julia API Documentation Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“– **Applying Documentation Rule**: [specific documentation principle being applied]" + +This ensures transparency about which documentation principle is being used and why. + +--- + +This document defines how the `docs/` directory of a Control Toolbox package is organised and built. It complements [`docstrings.md`](docstrings.md) (which covers *what* to write inside docstrings) by specifying *how* those docstrings are turned into a published documentation site via [Documenter.jl](https://documenter.juliadocs.org/) and [`CTBase.automatic_reference_documentation()`](https://control-toolbox.org/CTBase.jl/stable/guide/api-documentation.html). + +## Reference Implementations + +Three control-toolbox packages illustrate the spectrum of complexity. All three share the same skeleton; differences are stylistic. + +| Package | Scale | Notable features | +| --- | --- | --- | +| [`CTSolvers.jl/docs`](https://github.com/control-toolbox/CTSolvers.jl/tree/main/docs) | mid-weight package | Architecture page + Developer Guides + API Reference; `subdirectory="api"`; DocumenterMermaid | +| [`CTModels.jl/docs`](https://github.com/control-toolbox/CTModels.jl/tree/main/docs) | single package | Introduction + API Reference only; `subdirectory="."`; multiple extensions with `DocMeta.setdocmeta!`; Q&A-style Quick Start | +| [`OptimalControl.jl/docs`](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs) | meta-package | `DocumenterInterLinks` for cross-refs to dependencies; copies `Project.toml`/`Manifest.toml` to assets; documents only its Private API (Public is exposed via `@extref`) | + +## Core Principles + +1. **Auto-generated API reference** โ€” never hand-write API pages; use `CTBase.automatic_reference_documentation()`. +2. **One page per submodule** โ€” each submodule gets its own auto-generated API page. +3. **One page per loaded extension** โ€” each `Base.get_extension`-detected extension gets its own page when present. +4. **Public + private documented** โ€” both `public=true` and `private=true`, since users access internals via qualified paths (consistent with [`modules.md`](modules.md)). +5. **Hand-written guides separate from API** โ€” narrative guides live under `docs/src/guides/`; the API reference is generated. +6. **Index page is the entry point** โ€” `docs/src/index.md` provides admonitions, module table, guide links via `[@ref]`, and a Quick Start. +7. **Cross-references resolve at build time** โ€” every `[@extref]` in a docstring must be backed by an `InterLinks` entry in `make.jl`. + +## Directory Layout + +```text +docs/ +โ”œโ”€โ”€ Project.toml +โ”œโ”€โ”€ make.jl # entry point; uses with_api_reference() +โ”œโ”€โ”€ api_reference.jl # generate_api_reference() + with_api_reference() +โ”œโ”€โ”€ inventories/ # InterLinks fallback inventories (one per dependency) +โ”‚ โ”œโ”€โ”€ CTBase.toml +โ”‚ โ”œโ”€โ”€ CTModels.toml +โ”‚ โ””โ”€โ”€ CTSolvers.toml +โ””โ”€โ”€ src/ + โ”œโ”€โ”€ index.md # landing page + โ”œโ”€โ”€ architecture.md # narrative architecture page (optional) + โ”œโ”€โ”€ guides/ # hand-written guides + โ”‚ โ”œโ”€โ”€ implementing_a_modeler.md + โ”‚ โ”œโ”€โ”€ implementing_an_integrator.md + โ”‚ โ””โ”€โ”€ ... + โ””โ”€โ”€ api/ # auto-generated (cleaned up after build) +``` + +## Cross-Reference Infrastructure: DocumenterInterLinks + +For the `[@extref]` syntax (defined in [`docstrings.md`](docstrings.md)) to actually resolve at build time, `make.jl` must declare an `InterLinks` registry โ€” one entry per cross-referenced dependency: + +```julia +using DocumenterInterLinks + +links = InterLinks( + "CTBase" => ( + "https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml"), + ), + "CTModels" => ( + "https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml"), + ), + "CTSolvers" => ( + "https://control-toolbox.org/CTSolvers.jl/stable/", + "https://control-toolbox.org/CTSolvers.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTSolvers.toml"), + ), + # โ€ฆ one entry per dependency referenced via @extref +) +``` + +Each entry is a 3-tuple: stable docs URL, `objects.inv` URL (Sphinx-style inventory served by Documenter.jl), and a local TOML inventory under `docs/inventories/` used as fallback. Pass `links` to `makedocs` via the `plugins` argument: + +```julia +makedocs(; plugins=[links], ...) +``` + +This is what makes references like `` [`CTSolvers.Strategies.AbstractStrategy`](@extref) `` resolve to the dependency's published documentation. + +## `docs/make.jl` Template + +### Common skeleton + +```julia +pushfirst!(LOAD_PATH, joinpath(@__DIR__)) +pushfirst!(LOAD_PATH, joinpath(@__DIR__, "..")) + +using Documenter +using DocumenterInterLinks +using CTFlows +using CTBase +using Markdown +using MarkdownAST: MarkdownAST +# Optional: using DocumenterMermaid + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DocumenterReference extension (from CTBase) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) +if !isnothing(DocumenterReference) + DocumenterReference.reset_config!() +end + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DocMeta setup for the package and its extensions (loop pattern) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +Modules = [CTFlows, CTFlowsODE] # add extensions and dependencies as needed +for Module in Modules + isnothing(Module) && continue + isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && + DocMeta.setdocmeta!(Module, :DocTestSetup, :(using $Module); recursive=true) +end + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# InterLinks (only if @extref is used in docstrings) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +links = InterLinks( + "CTBase" => ("https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml")), + "CTModels" => ("https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml")), + "CTSolvers" => ("https://control-toolbox.org/CTSolvers.jl/stable/", + "https://control-toolbox.org/CTSolvers.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTSolvers.toml")), +) + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Paths and API reference generator +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +repo_url = "github.com/control-toolbox/CTFlows.jl" +src_dir = abspath(joinpath(@__DIR__, "..", "src")) +ext_dir = abspath(joinpath(@__DIR__, "..", "ext")) + +include("api_reference.jl") + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Build +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +with_api_reference(src_dir, ext_dir) do api_pages + makedocs(; + draft = false, + remotes = nothing, + warnonly = [:cross_references], + sitename = "CTFlows.jl", + plugins = [links], + format = Documenter.HTML(; + repolink = "https://" * repo_url, + prettyurls = false, + assets = [ + asset("https://control-toolbox.org/assets/css/documentation.css"), + asset("https://control-toolbox.org/assets/js/documentation.js"), + ], + ), + pages = [ + "Introduction" => "index.md", + "Architecture" => "architecture.md", + "Developer Guides" => [ + "Implementing a System" => "guides/implementing_a_system.md", + "Implementing a Flow Modeler" => "guides/implementing_a_flow_modeler.md", + "Implementing an ODE Integrator" => "guides/implementing_an_ode_integrator.md", + "Implementing an AD Backend" => "guides/implementing_an_ad_backend.md", + "Pipelines" => "guides/pipelines.md", + "Multi-phase Composition" => "guides/multi_phase_composition.md", + "Error Messages Reference" => "guides/error_messages.md", + ], + "API Reference" => api_pages, + ], + ) +end + +deploydocs(; repo=repo_url * ".git", devbranch="main") +``` + +### Variations + +`pages` structure: + +- **Light (CTModels-style)** โ€” `pages = ["Introduction" => "index.md", "API Reference" => api_pages]`. Use when there are no narrative guides. +- **Full (CTSolvers-style, recommended for CTFlows)** โ€” Architecture + guides + API Reference, as shown above. + +`warnonly` setting: + +- `warnonly = true` (CTSolvers) โ€” accept all build warnings. +- `warnonly = [:cross_references]` (CTModels, recommended for CTFlows) โ€” accept only cross-reference warnings. + +`prettyurls`: + +- `false` for local browsing during development. +- `true` for deployed documentation (omit or rely on default). + +## `docs/api_reference.jl` Template + +The file defines two public functions and one internal helper: + +- `generate_api_reference(src_dir, ext_dir) -> pages` โ€” builds the `pages` vector by calling `CTBase.automatic_reference_documentation` for each submodule and each loaded extension. +- `with_api_reference(f, src_dir, ext_dir)` โ€” wrapper that generates pages, calls `f(pages)`, then cleans up generated `.md` files via `_cleanup_pages` (in a `try/finally`). +- `_cleanup_pages(docs_src, pages)` โ€” recursive helper that deletes the auto-generated files after the build. + +### Submodule call + +```julia +CTBase.automatic_reference_documentation(; + subdirectory = "api", # "api" (CTSolvers) or "." (CTModels) + primary_modules = [ + CTFlows.Systems => src( + joinpath("Systems", "Systems.jl"), + joinpath("Systems", "abstract_system.jl"), + # ... all included files of the submodule + ), + ], + external_modules_to_document = [CTFlows], # include re-exported symbols + exclude = EXCLUDE_SYMBOLS, + public = true, + private = true, + title = "Systems", + title_in_menu = "Systems", + filename = "api_systems", # "api_*" (CTModels) or "*" (CTSolvers) +) +``` + +### Extension call (auto-detected) + +```julia +CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +if !isnothing(CTFlowsODE) + push!(pages, + CTBase.automatic_reference_documentation(; + subdirectory = "api", + primary_modules = [CTFlowsODE => ext("CTFlowsODE.jl")], + external_modules_to_document = [CTFlows], + exclude = EXCLUDE_SYMBOLS, + public = true, + private = true, + title = "ODE Extension", + title_in_menu = "ODE", + filename = "api_ext_ode", + ), + ) +end +``` + +### Variations on the API call + +- **`subdirectory`** โ€” `"api"` puts pages under `docs/src/api/`; `"."` puts them directly under `docs/src/`. Pick one and stay consistent. +- **`filename` prefix** โ€” `"api_systems"` (CTModels) makes auto-generated files visually distinct from hand-written ones; `"systems"` (CTSolvers) is fine when pages live under `api/`. +- **`external_modules_to_document`** โ€” set to `[CTFlows]` whenever a submodule re-exports symbols at package level (almost always). +- **`EXCLUDE_SYMBOLS`** โ€” start from `Symbol[:include, :eval]` and extend with package-specific noise (private macros, helper symbols leaked from `using`). + +### Cleanup helper + +```julia +function _cleanup_pages(docs_src::String, pages) + for p in pages + val = last(p) + if val isa AbstractString + fname = endswith(val, ".md") ? val : val * ".md" + full_path = joinpath(docs_src, fname) + if isfile(full_path) + rm(full_path) + println("Removed temporary API doc: $full_path") + end + elseif val isa AbstractVector + _cleanup_pages(docs_src, val) + end + end +end +``` + +### Meta-package variant (OptimalControl-style) + +When the package re-exports symbols from several control-toolbox dependencies, the public API is documented via `[@extref]` to those dependencies; the package itself only documents its **private** API: + +```julia +pages = [ + CTBase.automatic_reference_documentation(; + subdirectory = "api", + primary_modules = [ + OptimalControl => src(joinpath("helpers", "..."), ...) + ], + external_modules_to_document = [CTBase, CTModels, CTSolvers], + public = false, # public API lives in the dependencies + private = true, + title = "Private", + title_in_menu = "Private", + filename = "private", + ), +] +``` + +For CTFlows (single package), this variant does not apply โ€” it is documented here for completeness. + +### Optional: copy `Project.toml` / `Manifest.toml` to assets + +For packages where users may want to reproduce the exact documentation environment (typically the meta-package): + +```julia +mkpath(joinpath(@__DIR__, "src", "assets")) +cp(joinpath(@__DIR__, "Project.toml"), + joinpath(@__DIR__, "src", "assets", "Project.toml"); force=true) +cp(joinpath(@__DIR__, "Manifest.toml"), + joinpath(@__DIR__, "src", "assets", "Manifest.toml"); force=true) +``` + +Place these `cp` calls in `make.jl`, before `makedocs`. + +## `docs/src/index.md` Template + +Mandatory structure โ€” the *end* of the file (Documentation section + Quick Start) is what users land on first: + +````markdown +# CTFlows.jl + +```@meta +CurrentModule = CTFlows +``` + +The `CTFlows.jl` package is part of the [control-toolbox ecosystem](https://github.com/control-toolbox). +It provides the **flow layer** for optimal control problems: + +- **Systems** โ€” assembled callable objects (`AbstractSystem`) +- **Flows** โ€” system + integrator pairs (`AbstractFlow`) +- **Modelers** โ€” flow modeler strategies (`AbstractFlowModeler`) +- **Integrators** โ€” ODE integrator strategies (`AbstractODEIntegrator`) +- **AD Backends** โ€” automatic-differentiation strategies (`AbstractADBackend`) +- **Pipelines** โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` + +!!! info "CTFlows vs CTModels and CTSolvers" + **CTFlows** focuses on **flowing** dynamical systems associated with optimal control problems + (assembling systems, integrating ODEs, building solutions). + For **defining** the problems themselves, see [CTModels.jl](https://github.com/control-toolbox/CTModels.jl); + for **solving** them via discretisation and NLP, see [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl). + +!!! note + The root package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl) which aims + to provide tools to model and solve optimal control problems with ordinary differential equations + by direct and indirect methods, both on CPU and GPU. + +!!! warning "Qualified Module Access" + CTFlows does **not** export functions at the package level. All functions and types are + accessed via qualified module paths (consistent with the [submodule architecture](modules.md)): + + ```julia + using CTFlows + CTFlows.Systems.dimensions(sys) # โœ“ Qualified + CTFlows.Pipelines.build_system(input, m, ad) # โœ“ Qualified + ``` + +## Modules + +| Module | Purpose | +|--------|---------| +| `Core` | Shared types and utilities | +| `Systems` | `AbstractSystem`, concrete systems, `MultiPhaseSystem` | +| `Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow` | +| `Modelers` | `AbstractFlowModeler` and concrete modelers | +| `Integrators` | `AbstractODEIntegrator` and concrete integrators | +| `ADBackends` | `AbstractADBackend` and concrete backends | +| `Pipelines` | `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` | + +## Documentation + +### Developer Guides + +- [Architecture](@ref) โ€” module overview, type hierarchy, data flow +- [Implementing a System](@ref) โ€” `AbstractSystem` contract +- [Implementing a Flow Modeler](@ref) โ€” `AbstractFlowModeler` strategy +- [Implementing an ODE Integrator](@ref) โ€” `AbstractODEIntegrator` strategy +- [Implementing an AD Backend](@ref) โ€” `AbstractADBackend` strategy +- [Pipelines](@ref) โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` +- [Multi-phase Composition](@ref) โ€” `MultiPhaseSystem` and `MultiPhaseFlow` +- [Error Messages Reference](@ref) โ€” exception types with examples and fixes + +### API Reference + +Auto-generated documentation for all public and private symbols, organised by submodule. + +## Quick Start + +```julia +using CTFlows +using OrdinaryDiffEq # loads the ODE integration extension + +# Build a system from an OCP +sys = CTFlows.Pipelines.build_system((ocp, u), modeler, ad_backend) + +# Build a flow (system + integrator) +flow = CTFlows.Pipelines.build_flow(sys, integrator) + +# Integrate +sol = CTFlows.Pipelines.solve(flow, (t0, tf), x0, p0) +``` +```` + +### Quick Start variants + +- **Code-first (CTSolvers-style, shown above)** โ€” a short, runnable Julia code block illustrating typical usage with qualified paths. +- **Q&A (CTModels-style)** โ€” a list of "I want to ..." entries, each pointing to the relevant API page or guide. Useful when the API surface is wide. + +## Conventions + +### Admonitions + +| Type | Use | +| --- | --- | +| `!!! info` | Contrasting the package with siblings, scope statements | +| `!!! note` | Pointers to the root package or related material | +| `!!! warning` | Qualified-access policy, breaking caveats | +| `!!! tip` | Performance hints, idiomatic patterns | + +### Cross-references + +- `[Title](@ref)` โ€” in-package references (resolves to a heading or docstring in the current docs). +- `` [`Pkg.Submodule.sym`](@extref) `` โ€” references to symbols in a dependency with separate documentation. Requires the dependency to appear in `InterLinks`. + +See [`docstrings.md`](docstrings.md) for the full cross-reference policy. + +### Code examples + +Prefer fully qualified calls in examples (`CTFlows.Systems.dimensions(sys)`) โ€” consistent with [`modules.md`](modules.md). Exceptions: short snippets where the qualified form would obscure the point and the symbol is unambiguous. + +## Build Commands + +### Local build + +```bash +julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate(); include("docs/make.jl")' +``` + +### CI deployment + +Handled by `deploydocs(; repo=repo_url * ".git", devbranch="main")` at the bottom of `make.jl`. The standard control-toolbox GitHub Actions workflow takes care of the rest. + +## Quality Checklist + +Before finalising the documentation setup, verify: + +- [ ] `docs/make.jl` uses the `with_api_reference()` wrapper. +- [ ] `docs/api_reference.jl` exists with `generate_api_reference`, `with_api_reference`, and `_cleanup_pages` functions. +- [ ] `DocumenterReference` extension is loaded and reset via `reset_config!()`. +- [ ] If `[@extref]` is used in any docstring, `DocumenterInterLinks` is set up in `make.jl` with one `InterLinks` entry per cross-referenced dependency, and `links` is passed to `makedocs(; plugins=[links])`. +- [ ] One `automatic_reference_documentation` call per submodule, both `public=true` and `private=true`, with `external_modules_to_document=[CTFlows]` when relevant. +- [ ] Each known extension is detected via `Base.get_extension` and conditionally documented. +- [ ] `DocMeta.setdocmeta!` loop covers the package and its loaded extensions when doctests are used. +- [ ] `docs/src/index.md` contains: meta block, ecosystem link, info/note/warning admonitions, modules table, guide links via `[@ref]`, API reference note, and Quick Start. +- [ ] All cross-references use `[@ref]` / `[@extref]` correctly (see [`docstrings.md`](docstrings.md)). +- [ ] Hand-written guides are placed under `docs/src/guides/`. +- [ ] No hand-written API pages โ€” everything in `api/` is generated and cleaned up by `_cleanup_pages`. diff --git a/windsurf-rule-based/rules/exceptions.md b/windsurf-rule-based/rules/exceptions.md new file mode 100644 index 00000000..27a65a62 --- /dev/null +++ b/windsurf-rule-based/rules/exceptions.md @@ -0,0 +1,521 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Exception Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "โš ๏ธ **Applying Exception Rule**: [specific exception principle being applied]" + +This ensures transparency about which exception standard is being used and why. + +--- + +This document defines the exception handling standards for the Control Toolbox project. All error conditions must be handled using structured, informative exceptions that provide clear guidance to users. + +## Core Principles + +1. **Clear Messages**: Error messages must be immediately understandable +2. **Actionable Suggestions**: Provide guidance on how to fix the problem +3. **Rich Context**: Include what was expected, what was received, and where +4. **User-Friendly**: Format errors for end users, not just developers + +## Exception Types + +CTBase provides seven exception types, all subtypes of `CTException`. Import with: + +```julia +import CTBase.Exceptions +``` + +Catch all domain errors uniformly with: + +```julia +try + risky_operation() +catch e + if e isa Exceptions.CTException + handle_error(e) + else + rethrow() + end +end +``` + +**Hierarchy:** + +```text +CTException (abstract) +โ”œโ”€โ”€ IncorrectArgument # Invalid argument value +โ”œโ”€โ”€ PreconditionError # Wrong order / state violation +โ”œโ”€โ”€ NotImplemented # Interface stub +โ”œโ”€โ”€ ParsingError # DSL / syntax error +โ”œโ”€โ”€ AmbiguousDescription # Description tuple not found +โ”œโ”€โ”€ ExtensionError # Missing optional dependency +โ””โ”€โ”€ SolverFailure # Solver / integrator failure +``` + +--- + +### 1. IncorrectArgument + +Use when an individual argument is invalid or violates a precondition. + +**Fields:** + +- `msg::String`: Main error message (required) +- `got::Union{String, Nothing}`: What value was received (optional) +- `expected::Union{String, Nothing}`: What value was expected (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(IncorrectArgument("Invalid criterion")) + +# With got/expected +throw(IncorrectArgument( + "Invalid criterion", + got=":invalid", + expected=":min or :max" +)) + +# Full context +throw(IncorrectArgument( + "Invalid criterion", + got=":invalid", + expected=":min or :max", + suggestion="Use objective!(ocp, :min, ...) or objective!(ocp, :max, ...)", + context="objective! function" +)) +``` + +**When to use:** + +- Invalid function arguments +- Type mismatches +- Value out of range +- Missing required parameters +- Invalid combinations of parameters + +--- + +### 2. PreconditionError + +Use when a function call violates a precondition or is not allowed in the current state of the system. The arguments may be valid, but the *timing* or *state* is wrong. + +**Fields:** + +- `msg::String`: Main error message (required) +- `reason::Union{String, Nothing}`: Why the precondition failed (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(PreconditionError("State must be set before dynamics")) + +# With reason and suggestion +throw(PreconditionError( + "Cannot call state! twice", + reason="state has already been defined for this OCP", + suggestion="Create a new OCP instance" +)) + +# Full context +throw(PreconditionError( + "Cannot modify frozen OCP", + reason="OCP has been finalized and is immutable", + suggestion="Create a new OCP or modify before calling finalize!()", + context="constraint! function" +)) +``` + +**When to use:** + +- Functions called in the wrong order +- Operations on uninitialized objects +- State machine violations +- Workflow step dependencies + +**Distinction from `IncorrectArgument`:** + +- `IncorrectArgument`: the *value* of an argument is wrong +- `PreconditionError`: the *timing* or *state* is wrong + +--- + +### 3. NotImplemented + +Use to mark interface points that must be implemented by concrete subtypes. + +**Fields:** + +- `msg::String`: Description of what is not implemented (required) +- `required_method::Union{String, Nothing}`: Method signature that needs implementation (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(NotImplemented("solve! not implemented for MyStrategy")) + +# With required_method and suggestion +throw(NotImplemented( + "Method solve! not implemented", + required_method="solve!(::MyStrategy, ...)", + suggestion="Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" +)) + +# For abstract type contracts +abstract type AbstractStrategy end + +function solve!(strategy::AbstractStrategy, problem) + throw(NotImplemented( + "solve! must be implemented for each strategy type", + required_method="solve!(::$(typeof(strategy)), problem)", + suggestion="Define solve!(::$(typeof(strategy)), problem)", + context="strategy dispatch" + )) +end +``` + +**When to use:** + +- Abstract type interface methods +- Extension points +- Optional features not yet implemented +- Platform-specific functionality + +--- + +### 4. ParsingError + +Use for parsing errors in DSLs or structured input. + +**Fields:** + +- `msg::String`: Description of the parsing error (required) +- `location::Union{String, Nothing}`: Where in the input the error occurred (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) + +**Examples:** + +```julia +# Simple message +throw(ParsingError("Unexpected token 'end'")) + +# With location and suggestion +throw(ParsingError( + "Unexpected token 'end'", + location="line 42, column 15", + suggestion="Check syntax balance or remove extra 'end'" +)) +``` + +**When to use:** + +- DSL parsing errors +- Configuration file parsing +- Input validation during parsing +- Syntax errors + +--- + +### 5. AmbiguousDescription + +Use when a description (a tuple of `Symbol`s) cannot be matched to any known valid description in a catalogue. + +**Fields:** + +- `description::Tuple{Vararg{Symbol}}`: The ambiguous or incorrect description tuple (required) +- `candidates::Union{Vector{String}, Nothing}`: Suggested valid alternatives (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Constructor:** `AmbiguousDescription(description; msg=..., candidates=..., suggestion=..., context=...)` + +**Examples:** + +```julia +# Simple +throw(AmbiguousDescription((:f,))) + +# With candidates and suggestion +throw(AmbiguousDescription( + (:descent,), + candidates=["(:descent, :bfgs, :bisection)", "(:descent, :gradient, :fixedstep)"], + suggestion="Use a complete description like (:descent, :bfgs, :bisection)", + context="algorithm selection" +)) +``` + +**When to use:** + +- Description-based APIs where a partial `Symbol` tuple doesn't match any catalogue entry +- Algorithm selection via symbolic descriptions +- Pattern matching in mathematical modeling DSLs + +--- + +### 6. ExtensionError + +Use when a feature requires optional dependencies (weak dependencies) that are not loaded. + +**Fields:** + +- `weakdeps::Tuple{Vararg{Symbol}}`: Names of missing packages +- `feature::Union{String, Nothing}`: Which feature requires them (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Constructor:** `ExtensionError(pkgs::Symbol...; message="", feature=nothing, context=nothing)` + +โš ๏ธ `ExtensionError()` with no arguments throws `PreconditionError` instead. + +**Examples:** + +```julia +# Single missing dependency +throw(ExtensionError(:Plots)) + +# With feature description +throw(ExtensionError( + :Plots, + feature="result visualization", + context="plot_results function" +)) + +# Multiple missing dependencies +throw(ExtensionError(:SciMLBase, :OrdinaryDiffEq; + message="to integrate ODEs", + feature="ODE integration", + context="build_flow" +)) +``` + +**When to use:** + +- Extension stubs in `ext/` files (SciML, ForwardDiff, Plots, StaticArrays) +- Weak dependency not loaded by the user +- ODE integration, plotting, AD functionality behind extensions + +--- + +### 7. SolverFailure + +Use when a solver (ODE integrator, optimization solver, linear solver) fails to complete successfully. + +**Fields:** + +- `msg::String`: Error message describing the failure (required) +- `retcode::Union{String, Nothing}`: Solver-specific return code (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple +throw(SolverFailure("ODE integration failed")) + +# With SciML retcode +throw(SolverFailure( + "ODE integration failed", + retcode=":Unstable", + suggestion="Reduce time step or check initial conditions", + context="SciML integrator in build_flow" +)) + +# Optimization solver +throw(SolverFailure( + "Optimization solver did not converge", + retcode=":MaxIterations", + suggestion="Increase max iterations or adjust tolerance settings", + context="IPOPT solver in CTDirect" +)) +``` + +**Common SciML return codes:** `:Unstable`, `:DtLessThanMin`, `:MaxIters`, `:Success` + +**When to use:** + +- ODE integration failures in CTFlows (SciML integrators) +- Non-convergence of optimization solvers +- Ill-conditioned linear systems +- Any numerical solver returning a failure status + +**Distinction from other exceptions:** + +- `IncorrectArgument`: the *input* is invalid +- `PreconditionError`: the *state* or *timing* is wrong +- `SolverFailure`: the *numerical computation* itself failed + +--- + +## Quick Reference + +| Situation | Exception | +| --- | --- | +| Invalid argument value | `IncorrectArgument` | +| Wrong function call order / state | `PreconditionError` | +| Unimplemented interface method | `NotImplemented` | +| DSL / syntax parsing error | `ParsingError` | +| Description tuple not matched | `AmbiguousDescription` | +| Missing optional dependency | `ExtensionError` | +| Solver / integrator failure | `SolverFailure` | + +--- + +## Best Practices + +### Write Clear Messages + +**โœ… Good - Specific and clear:** + +```julia +throw(IncorrectArgument( + "State dimension must be positive", + got="n = -1", + expected="n > 0", + suggestion="Provide a positive integer for state dimension" +)) +``` + +**โŒ Bad - Vague:** + +```julia +throw(IncorrectArgument("Invalid input")) +``` + +### Use Appropriate Exception Types + +**โœ… Good - Correct type:** + +```julia +throw(IncorrectArgument("n must be positive", got="n = -1", expected="n > 0")) +throw(PreconditionError("Cannot modify frozen OCP", reason="OCP is immutable")) +throw(NotImplemented("solve! not implemented", required_method="solve!(::MyStrategy, ...)")) +``` + +**โŒ Bad - Wrong type:** + +```julia +throw(IncorrectArgument("OCP already finalized")) # Should be PreconditionError +throw(PreconditionError("n must be positive")) # Should be IncorrectArgument +``` + +--- + +## Common Patterns + +### Validation Pattern + +```julia +function validate_dimension(n::Int, name::String) + if n <= 0 + throw(IncorrectArgument( + "Dimension must be positive", + got="$name = $n", + expected="$name > 0", + suggestion="Provide a positive integer for $name" + )) + end +end +``` + +### State Machine Pattern + +```julia +mutable struct OCP + state_defined::Bool +end + +function state!(ocp::OCP, n::Int) + if ocp.state_defined + throw(PreconditionError( + "Cannot call state! twice", + reason="state has already been defined for this OCP", + suggestion="Create a new OCP instance" + )) + end + ocp.state_defined = true +end +``` + +### Interface Pattern + +```julia +abstract type AbstractStrategy end + +function solve!(strategy::AbstractStrategy, problem) + throw(NotImplemented( + "solve! must be implemented for each strategy type", + required_method="solve!(::$(typeof(strategy)), problem)", + suggestion="Define solve!(::$(typeof(strategy)), problem) or import the relevant package" + )) +end +``` + +--- + +## Testing Exceptions + +```julia +@testset "Exception Types" begin + @test_throws IncorrectArgument invalid_function(bad_arg) + + err = try + invalid_function(bad_arg) + catch e + e + end + @test err isa IncorrectArgument + @test occursin("Invalid criterion", err.msg) +end +``` + +--- + +## Quality Checklist + +Before finalizing exception handling, verify: + +- [ ] Exception type is appropriate (IncorrectArgument, PreconditionError, NotImplemented, ParsingError, AmbiguousDescription, ExtensionError, SolverFailure) +- [ ] Error message is clear and specific +- [ ] `got` and `expected` fields provided when applicable +- [ ] Actionable `suggestion` provided +- [ ] `context` provided for complex errors +- [ ] Exception is tested with `@test_throws` +- [ ] Error message is user-friendly (no jargon) +- [ ] Suggestion is concrete and actionable + +--- + +## Anti-Patterns + +```julia +# โŒ Generic errors +error("Something went wrong") + +# โŒ Missing context +throw(IncorrectArgument("Invalid value")) + +# โŒ No suggestions +throw(IncorrectArgument("Unknown constraint type", got=":boundary")) +``` + +--- + +## Related Rules + +- `.windsurf/rules/docstrings.md` - Document exceptions in `# Throws` section +- `.windsurf/rules/architecture.md` - Error handling architecture +- `.windsurf/rules/testing.md` - Exception testing patterns diff --git a/windsurf-rule-based/rules/modules.md b/windsurf-rule-based/rules/modules.md new file mode 100644 index 00000000..1bbd1278 --- /dev/null +++ b/windsurf-rule-based/rules/modules.md @@ -0,0 +1,350 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Submodule Architecture Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ—๏ธ **Applying Modules Rule**: [specific principle being applied]" + +This ensures transparency about which submodule-architecture principle is being used and why. + +--- + +This document defines the submodule organisation, import conventions, and qualification rules for Julia code in the Control Toolbox ecosystem. The reference implementation is [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl); every package in the stack (including CTFlows) must follow the same pattern so that code, tests, and cross-package imports remain predictable and stable. + +## Core Principles + +1. **One submodule per concern** โ€” each submodule lives in its own subdirectory `src//.jl` and has a single, well-defined responsibility. +2. **Manifest-only top-level** โ€” the package's top-level file only `include`s subdirectories and does `using .Submodule`; **it exports nothing**. +3. **Submodules export their public API** โ€” every submodule declares `export` for the symbols it considers public; internal helpers stay unexported and are reached via full qualification. +4. **Qualified imports for external packages** โ€” use `using PackageName: PackageName` or `import PackageName.SubModule` instead of bare `using`. +5. **Qualified usage everywhere** โ€” always call sibling-module or external symbols as `Submodule.function` / `Submodule.Type`; never rely on implicit scope. +6. **One-way dependency flow** โ€” submodules form a DAG; lower-level modules cannot import higher-level ones, and there are no cycles. + +## Submodule Directory Layout + +Each submodule occupies its own subdirectory. The `.jl` file at the root of that directory is the **manifest**; every other `.jl` file is `include`d by the manifest (possibly from nested subdirectories). + +Reference layout (CTSolvers): + +```text +src/ +โ”œโ”€โ”€ CTSolvers.jl # top-level manifest (exports nothing) +โ”œโ”€โ”€ Core/ +โ”‚ โ””โ”€โ”€ Core.jl # Core manifest +โ”œโ”€โ”€ Options/ +โ”‚ โ”œโ”€โ”€ Options.jl # manifest +โ”‚ โ”œโ”€โ”€ not_provided.jl +โ”‚ โ”œโ”€โ”€ option_value.jl +โ”‚ โ”œโ”€โ”€ option_definition.jl +โ”‚ โ””โ”€โ”€ extraction.jl +โ”œโ”€โ”€ Strategies/ +โ”‚ โ”œโ”€โ”€ Strategies.jl # manifest +โ”‚ โ”œโ”€โ”€ display_formatting.jl +โ”‚ โ”œโ”€โ”€ contract/ # nested subdirectory +โ”‚ โ”‚ โ”œโ”€โ”€ abstract_strategy.jl +โ”‚ โ”‚ โ”œโ”€โ”€ metadata.jl +โ”‚ โ”‚ โ””โ”€โ”€ strategy_options.jl +โ”‚ โ””โ”€โ”€ api/ +โ”‚ โ”œโ”€โ”€ registry.jl +โ”‚ โ”œโ”€โ”€ builders.jl +โ”‚ โ””โ”€โ”€ โ€ฆ +โ”œโ”€โ”€ Optimization/ +โ”œโ”€โ”€ Orchestration/ +โ”œโ”€โ”€ Modelers/ +โ”œโ”€โ”€ DOCP/ +โ””โ”€โ”€ Solvers/ +``` + +Rules: + +- A submodule directory contains exactly one manifest named after the module. +- Nested subdirectories (`contract/`, `api/`, โ€ฆ) are allowed for organisation. +- No logic in the manifest โ€” only imports, includes, and exports. + +## The Submodule Manifest Pattern + +Canonical structure of `src//.jl`: + +```julia +""" +Module docstring โ€” purpose, responsibilities, dependencies. +""" +module Name + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using SolverCore: SolverCore +using ADNLPModels: ADNLPModels + +# 2. Internal sibling-submodule imports +using ..Options +using ..Strategies +import ..Core as CTCore +using ..Core: AbstractTag + +# 3. Include files (ordered by internal dependency) +include(joinpath(@__DIR__, "abstract_types.jl")) +include(joinpath(@__DIR__, "contract.jl")) +include(joinpath(@__DIR__, "builders.jl")) + +# 4. Public API โ€” exports only +export AbstractX, ConcreteX +export build_x, validate_x + +end # module Name +``` + +Ordering rules: + +1. Docstring first (module-level documentation). +2. `module` declaration. +3. External-package imports. +4. Internal sibling imports. +5. `include(...)` calls (in dependency order). +6. `export` statements. +7. `end # module Name`. + +Section separators (`# ===โ€ฆ===`) are encouraged for readability. + +## External Package Import Style + +Three acceptable patterns, in order of preference: + +### 1. Name-qualified `using` (preferred for packages) + +```julia +using SolverCore: SolverCore +using ADNLPModels: ADNLPModels +``` + +This brings only the module name into scope. Call sites use `SolverCore.solve(...)`, `ADNLPModels.ADNLPModel(...)`. + +### 2. Submodule `import` + +```julia +import CTBase.Exceptions +``` + +This makes `Exceptions` available as a qualifier but does not bring its exported symbols into scope. Call sites use `Exceptions.NotImplemented(...)`, `Exceptions.IncorrectArgument(...)`. + +### 3. Symbol-qualified `import` (reserved for macros and heavily-used single symbols) + +```julia +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +``` + +Use only for: + +- Macros (cannot be qualified at usage site without awkwardness). +- Single symbols used pervasively where qualification would add noise (rare). + +### Forbidden patterns + +```julia +# โŒ Bare using โ€” pollutes namespace +using ADNLPModels + +# โŒ Symbol-list using โ€” opaque origin at call sites +using CTBase: AbstractModel, Dimension, validate, check +``` + +## Internal Submodule Import Style + +From within a submodule manifest, sibling submodules are reached via the parent scope `..`. + +### Importing a whole sibling + +```julia +using ..Options +using ..Strategies +``` + +Brings the submodule name into scope. Call sites use `Options.extract_option(...)`, `Strategies.metadata(...)`. + +### Aliasing + +```julia +import ..Core as CTCore +``` + +Use when the original name would conflict with another symbol, or when a shorter internal handle improves readability. + +### Specific symbol from a sibling + +```julia +using ..Core: AbstractTag +``` + +Use only when the symbol is pervasive within the current submodule *and* using it unqualified is unambiguous (typically for abstract types inherited from a Core module). + +## Qualification at Call Sites + +All references to external or sibling-submodule symbols must be qualified. This is the main consequence of the import conventions above. + +**โœ… Correct:** + +```julia +function Strategies.metadata(::Type{<:Modelers.ADNLP{P}}) where {P<:CPU} + return Strategies.StrategyMetadata( + Strategies.OptionDefinition( + name=:backend, type=Symbol, default=:optimized, + description="AD backend used by ADNLPModels", + ), + ) +end + +throw( + Exceptions.NotImplemented( + "Model building not implemented"; + required_method = "(modeler::$(typeof(modeler)))(prob::Optimization.AbstractOptimizationProblem, initial_guess)", + suggestion = "Implement the callable method for $(typeof(modeler)) to build NLP models", + context = "AbstractNLPModeler - required method implementation", + ), +) +``` + +**โŒ Wrong โ€” unqualified, fragile to refactoring:** + +```julia +# Where does StrategyMetadata come from? ADNLP? CPU? +function metadata(::Type{<:ADNLP{P}}) where {P<:CPU} + return StrategyMetadata( + OptionDefinition(name=:backend, type=Symbol, default=:optimized), + ) +end +``` + +### Why qualification matters + +- **Explicit origin at every call site** โ€” readers immediately see which submodule a symbol belongs to. +- **Refactor safety** โ€” if `Strategies` is renamed or moved, only the `using ..Strategies` line changes; all call sites stay correct as long as the new import uses the same name (or is aliased to it). +- **No accidental shadowing** โ€” qualified names cannot be captured by a local variable with the same stem. +- **Cross-package consistency** โ€” the same pattern works for external packages (`Exceptions.NotImplemented`) and sibling submodules (`Strategies.metadata`). + +## Dependency Order and the DAG + +The loading order in the top-level manifest must reflect a correct topological order of submodule dependencies. + +Reference DAG (CTSolvers): + +```text +Core + โ”œโ”€โ”€ Options + โ”‚ โ””โ”€โ”€ Strategies + โ”‚ โ”œโ”€โ”€ Orchestration + โ”‚ โ””โ”€โ”€ Optimization + โ”‚ โ”œโ”€โ”€ Modelers + โ”‚ โ”‚ โ””โ”€โ”€ DOCP + โ”‚ โ”‚ โ””โ”€โ”€ Solvers +``` + +Rules: + +- The top-level manifest lists `include`/`using` calls in topological order. +- A submodule can only `using ..Lower` where `Lower` was already loaded. +- **No cycles** โ€” if two submodules need each other, extract the shared concern into a lower-level submodule (typically `Core` or a dedicated `Types` module). + +## Exports and Public API + +Two-level rule: + +- **Submodule level** โ€” each submodule declares `export` at the end of its manifest for the symbols that form its public API. Internal helpers (names prefixed with `_`, or kept unexported by convention) stay unexported and are reached via full qualification. +- **Top-level (package) level** โ€” the package manifest **exports nothing**. It only loads submodules with `using .Submodule` so they become accessible as `Package.Submodule`. There are **no `export` statements** at the top level. + +### Consequences + +- Users access public symbols via `Package.Submodule.sym` โ€” explicit, stable, self-documenting. +- Adding a new public symbol in a submodule is a local change (one `export` line). +- Moving a symbol between submodules is visible at the call site (the qualification changes). +- Namespace conflicts between submodules cannot occur at package load time, because nothing is brought into the package-level scope. + +### Top-level manifest example + +```julia +""" + CTFlows + +Brief description of the package. + +# Architecture Overview + +CTFlows is organised into specialised submodules; all public symbols are +accessed via qualified paths (e.g. `CTFlows.Systems.AbstractSystem`). +""" +module CTFlows + +include(joinpath(@__DIR__, "Core", "Core.jl")) +using .Core + +include(joinpath(@__DIR__, "Systems", "Systems.jl")) +using .Systems + +include(joinpath(@__DIR__, "Modelers", "Modelers.jl")) +using .Modelers + +# โ€ฆ more submodules โ€ฆ + +# NO export statements here. + +end # module CTFlows +``` + +### User access patterns + +```julia +using CTFlows # brings no symbols into scope directly +CTFlows.Systems.AbstractSystem # fully qualified (recommended) + +using CTFlows.Systems # brings Systems exports into scope +AbstractSystem # unqualified (user's choice, at their own risk) +``` + +The `export` inside each submodule makes the unqualified form available to users who explicitly opt in via `using CTFlows.Submodule`. The package-level `using CTFlows` remains silent. + +## Proposed CTFlows Layout + +Informed by [`reports/design.md`](../../reports/design.md), the CTFlows submodule breakdown mirrors CTSolvers' separation of concerns: + +```text +src/ +โ”œโ”€โ”€ CTFlows.jl # top-level manifest, exports nothing +โ”œโ”€โ”€ Core/Core.jl # shared types and utilities +โ”œโ”€โ”€ Systems/Systems.jl # AbstractSystem + concrete systems + MultiPhaseSystem +โ”œโ”€โ”€ Flows/Flows.jl # AbstractFlow, Flow, MultiPhaseFlow +โ”œโ”€โ”€ Modelers/Modelers.jl # AbstractFlowModeler + concrete modelers +โ”œโ”€โ”€ Integrators/Integrators.jl # AbstractODEIntegrator + concrete integrators +โ”œโ”€โ”€ ADBackends/ADBackends.jl # AbstractADBackend + concrete backends +โ””โ”€โ”€ Pipelines/Pipelines.jl # build_system, build_flow, integrate, build_solution, solve +``` + +Dependency order (topologically sorted): + +```text +Core + โ”œโ”€โ”€ Systems + โ”œโ”€โ”€ Integrators + โ”œโ”€โ”€ ADBackends + โ”œโ”€โ”€ Modelers (depends on Systems, ADBackends) + โ”œโ”€โ”€ Flows (depends on Systems, Integrators) + โ””โ”€โ”€ Pipelines (depends on all of the above) +``` + +The `Options` and `Strategies` infrastructure is consumed from CTSolvers via standard package imports (`using CTSolvers: CTSolvers` then qualified calls like `CTSolvers.Strategies.AbstractStrategy`). + +## Quality Checklist + +Before finalising a submodule or a package restructure, verify: + +- [ ] Each submodule lives in its own subdirectory with a `.jl` manifest. +- [ ] The manifest contains only a docstring, `module`, imports, `include`s, `export`s, and `end`. +- [ ] External-package imports use `using Pkg: Pkg`, `import Pkg.Sub`, or (for macros) `import Pkg: sym`. +- [ ] Internal imports use `using ..Sibling`, `import ..Sibling as Alias`, or `using ..Sibling: Sym`. +- [ ] All references to sibling or external symbols are fully qualified at call sites. +- [ ] The dependency graph is acyclic and respected by the top-level loading order. +- [ ] Each submodule declares `export` for its public API. +- [ ] The top-level package manifest contains **no** `export` statements. diff --git a/windsurf-rule-based/rules/performance.md b/windsurf-rule-based/rules/performance.md new file mode 100644 index 00000000..2509ab12 --- /dev/null +++ b/windsurf-rule-based/rules/performance.md @@ -0,0 +1,304 @@ +--- +trigger: model_decision +--- + +# Julia Performance and Type Stability Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "โšก **Applying Performance Rule**: [specific performance principle being applied]" + +This ensures transparency about which performance standard is being used and why. + +--- + +This document defines performance and type stability standards for the Control Toolbox project. Performance-critical code must follow these guidelines to ensure optimal execution speed and memory efficiency. + +## Core Principles + +1. **Measure First**: Profile before optimizing +2. **Focus on Hot Paths**: Optimize where it matters (inner loops, critical functions) +3. **Type Stability**: Ensure type-stable code (see `type-stability` rule) +4. **Avoid Premature Optimization**: Optimize only when necessary +5. **Maintain Readability**: Don't sacrifice clarity for marginal gains + +## Performance Hierarchy + +### Critical (Must Optimize) + +- Inner loops (called millions of times) +- Numerical computations in solvers +- Hot paths identified by profiling +- Real-time systems + +### Important (Should Optimize) + +- Frequently called functions +- Data processing pipelines +- API functions with performance requirements + +### Low Priority (Optimize if Easy) + +- One-time setup code +- User-facing convenience functions +- Error handling paths +- Debugging utilities + +## Profiling + +### Using Profile.jl + +```julia +using Profile + +@profile my_function(args...) +Profile.print() +Profile.clear() +@profile (for i in 1:1000; my_function(args...); end) +``` + +### Using ProfileView.jl + +```julia +using ProfileView + +@profview my_function(args...) +@profview for i in 1:1000 + my_function(args...) +end +``` + +**Interpreting Results:** +- **Red bars**: Hot spots (most time spent) +- **Wide bars**: Functions called many times +- **Type instabilities**: Yellow/red warnings + +## Benchmarking + +### Using BenchmarkTools.jl + +```julia +using BenchmarkTools + +@benchmark my_function($args...) + +b1 = @benchmark old_implementation($args...) +b2 = @benchmark new_implementation($args...) +judge(median(b2), median(b1)) +``` + +**Best Practices:** + +```julia +# โœ… Interpolate variables (avoids global variable penalty) +x = rand(1000) +@benchmark my_function($x) + +# โœ… Warm up before benchmarking +my_function(args...) +@benchmark my_function($args...) +``` + +## Memory Allocations + +### Reducing Allocations + +**โœ… Good - Preallocate buffers:** + +```julia +function process_data!(output, input) + for i in eachindex(input) + output[i] = input[i]^2 + end + return output +end + +output = similar(input) +process_data!(output, input) # No allocations +``` + +**โŒ Bad - Allocate in loop:** + +```julia +function process_data(input) + output = [] + for x in input + push!(output, x^2) # Allocates each iteration + end + return output +end +``` + +**โœ… Good - Use views instead of copies:** + +```julia +sub = @view matrix[1:10, :] # No allocation +``` + +**โœ… Good - In-place operations:** + +```julia +A .= B .+ C # In-place, no allocation +``` + +## Common Optimizations + +### 1. Avoid Global Variables + +```julia +# โŒ Bad +global_counter = 0 + +# โœ… Good +const COUNTER = Ref(0) +function increment() + COUNTER[] += 1 +end +``` + +### 2. Use @inbounds for Bounds-Checked Loops + +```julia +function sum_array(arr) + s = zero(eltype(arr)) + @inbounds for i in eachindex(arr) + s += arr[i] + end + return s +end +``` + +**โš ๏ธ Warning:** `@inbounds` disables bounds checking. Use only when safe. + +### 3. Use @simd for Vectorization + +```julia +function sum_array(arr) + s = zero(eltype(arr)) + @simd for i in eachindex(arr) + s += arr[i] + end + return s +end +``` + +### 4. Use StaticArrays for Small Arrays + +```julia +using StaticArrays + +v = SVector(1.0, 2.0, 3.0) +m = SMatrix{3,3}(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) +result = m * v # No allocation! +``` + +### 5. Avoid Untyped Containers + +```julia +# โŒ Bad +results = [] # Vector{Any} + +# โœ… Good +results = Float64[] +``` + +### 6. Use Multiple Dispatch Effectively + +```julia +function process(x) # Generic fallback +end + +function process(x::Float64) # Fast specialized method +end +``` + +## Performance Testing + +### Allocation Tests + +```julia +@testset "Allocations" begin + x = rand(1000) + allocs = @allocated process!(x) + @test allocs == 0 + + allocs = @allocated build_model(x) + @test allocs < 1000 # bytes +end +``` + +### Benchmark Tests + +```julia +@testset "Performance" begin + x = rand(1000) + b = @benchmark process($x) + @test median(b.times) < 1_000_000 # < 1ms + @test b.allocs == 0 +end +``` + +## Optimization Workflow + +1. **Profile** โ€” `@profview my_application()` +2. **Measure baseline** โ€” `baseline = @benchmark critical_function($args...)` +3. **Optimize** โ€” fix type instabilities, reduce allocations, use specialized algorithms +4. **Measure improvement** โ€” compare `median(baseline.times)` vs `median(optimized.times)` +5. **Verify correctness** โ€” `@test optimized_function(args...) โ‰ˆ baseline_function(args...)` + +## When NOT to Optimize + +**โŒ Don't optimize:** +- Before profiling +- Code that runs once +- Code that's already fast enough +- At the expense of readability + +## Parallelization + +### Using Threads + +```julia +using Base.Threads + +function parallel_sum(arr) + sums = zeros(nthreads()) + @threads for i in eachindex(arr) + sums[threadid()] += arr[i] + end + return sum(sums) +end +``` + +**Good candidates for parallelization:** independent computations, large data sets, CPU-bound tasks. + +## Quality Checklist + +Before finalizing performance optimizations: + +- [ ] Profiled to identify bottlenecks +- [ ] Benchmarked baseline performance +- [ ] Optimized critical paths only +- [ ] Verified type stability with `@inferred` +- [ ] Tested allocations are acceptable +- [ ] Verified correctness after optimization +- [ ] Maintained code readability +- [ ] Measured actual improvement + +## Tools Reference + +| Tool | Purpose | +|---|---| +| `Profile.jl` | Built-in profiling | +| `ProfileView.jl` | Visual profiling | +| `BenchmarkTools.jl` | Precise benchmarking | +| `@time` | Quick timing | +| `@allocated` | Allocation tracking | +| `@code_warntype` | Type stability | +| `StaticArrays.jl` | Fast small arrays | + +## Related Skills + +- `type-stability` rule โ€” type stability standards (critical for performance) +- `testing-creation` rule โ€” performance testing patterns +- `architecture` rule โ€” architecture patterns that affect performance diff --git a/windsurf-rule-based/rules/plan.md b/windsurf-rule-based/rules/plan.md new file mode 100644 index 00000000..2f9c3350 --- /dev/null +++ b/windsurf-rule-based/rules/plan.md @@ -0,0 +1,308 @@ +--- +trigger: model_decision +--- + +# Julia Implementation Plan Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“‹ **Applying Plan Rule**: [specific planning principle being applied]" + +This ensures transparency about which planning standard is being used and why. + +--- + +This document defines how to produce a complete, actionable implementation plan before writing any code. Plans are written first; implementation follows the plan step by step. **Docstrings are never written during implementation โ€” they are a dedicated final step.** + +## Core Principles + +1. **Plan before code** โ€” a plan must be fully written and reviewed before any file is touched. +2. **One step = one atomic change** โ€” each step modifies a single file or a single cohesive concern. Steps must be independently reviewable. +3. **Dependency-aware ordering** โ€” steps must respect the DAG of submodule dependencies. A file that depends on another always comes after it. +4. **Interleaved test checkpoints** โ€” instead of a massive test phase at the end, plans must include regular test checkpoints after logical groups of implementation steps. This provides validation before moving to the next phase. +5. **Docstrings are last** โ€” no docstring is written during implementation steps. A single dedicated step at the very end of the plan writes all docstrings at once, when the code is stable. +6. **No silent assumptions** โ€” any architectural decision made during planning must be stated explicitly in the plan (load-order changes, new exports, removed symbols, etc.). +7. **Rules are cited at the point of use** โ€” every step that triggers a rule from `.windsurf/rules/` must name that rule explicitly so the implementer knows which standard to follow. + +## CTFlows Project Configuration + +### Package + +- **Name**: `CTFlows` +- **Base branch**: `develop` +- **Test entry point**: `test/runtests.jl` +- **Test subdirectory**: `test/suite//test_.jl` +- **Extension directory**: `ext/` + +### Module Dependency DAG + +```text +Common + โ†“ +Data + โ†“ +Systems + โ†“ +Integrators + โ†“ +Flows + โ†“ +Solutions + +``` + +### Submodule List + +| Submodule | Source file | Purpose | +| --- | --- | --- | +| `Common` | `src/Common/Common.jl` | AbstractTag, AbstractTrait, traits, ODE params | +| `Data` | `src/Data/Data.jl` | VectorField, HamiltonianVectorField | +| `Systems` | `src/Systems/Systems.jl` | AbstractSystem subtypes | +| `Integrators` | `src/Integrators/Integrators.jl` | AbstractIntegrator, result types | +| `Flows` | `src/Flows/Flows.jl` | AbstractFlow, Flow, MultiPhaseFlow | +| `Solutions` | `src/Solutions/Solutions.jl` | Solution building and accessors | + +### Test Subdirectory Map + +| Module(s) under test | Test subdirectory | +| --- | --- | +| Common | `suite/common/` | +| Data | `suite/data/` | +| Systems | `suite/systems/` | +| Integrators | `suite/integrators/` | +| Flows | `suite/flows/` | +| Solutions | `suite/solutions/` | + +## Rules Reference + +The following rules from `.windsurf/rules/` govern implementation. The plan must mention each rule **at the step where it applies**, not just once globally. + +| Rule | Scope | Typical trigger in a plan | +| --- | --- | --- | +| `architecture` | SOLID principles, patterns, module organisation | Any step that introduces a new type, new dependency, or restructures a module | +| `modules` (rule) | Submodule manifests, import style, qualification, export declarations | Any step touching a `.jl` manifest, import list, or `export` block | +| `exceptions` (rule) | Structured exceptions: `IncorrectArgument`, `PreconditionError`, `NotImplemented`, `ParsingError`, `AmbiguousDescription`, `ExtensionError`, `SolverFailure` | Any step adding a stub, a contract method, or an error path | +| `testing-creation` (rule) | Test structure, fake types at top-level, unit/integration/contract/error separation | Every test step | +| `testing-execution` (rule) | Running tests, coverage reports | The verification step | +| `type-stability` (rule) | `@inferred`, parametric types, avoiding `Any` | Steps introducing new structs or performance-critical functions | +| `performance` (rule) | Profiling, benchmarking, allocation reduction | Steps on hot paths or after type-stability work | +| `docstrings` (rule) | Docstring templates, `$(TYPEDEF)`, `$(TYPEDSIGNATURES)`, cross-references | The dedicated docstring step only | +| `documentation` (rule) | `docs/` organisation, `make.jl`, API reference generation | If the plan includes a documentation update step | + +## Plan Structure + +A valid plan contains the following sections **in order**: + +### 1. Title and summary + +```markdown +# + +<One-paragraph summary: what changes, why it changes, and what the end state looks like.> +``` + +### 2. What changes and why + +Describe: + +- **What is being changed**: files, symbols, types, interfaces. +- **Why**: the architectural motivation (decoupling, new contract, load-order fix, etc.). +- **What disappears**: explicitly list every symbol, callable, or file that is deleted. +- **What is added**: new files, new types, new exports. + +### 3. Dependency graph after the change + +Always include a dependency diagram showing the new module/file relationships: + +```text +Module A โ†’ Module B, Module C +Module B โ†’ Module D +``` + +Use the same notation as the existing codebase. This graph drives step ordering in section 5. + +### 4. Branch step + +Always start with: + +```markdown +### Step 0 โ€” Branch + +\`\`\`bash +git checkout develop && git pull +git checkout -b <branch-name> +\`\`\` +``` + +**Note:** Use `develop` as the base branch for CTFlows. + +### 5. Implementation steps + +Number every step starting from 1. Each step must follow this template: + +```markdown +### Step N โ€” `path/to/file.jl` [(new file) | (modified)] + +> ๐Ÿ“ Follow `architecture` rule โ€” [specific principle, e.g. "new abstract type follows the contract pattern"] +> ๐Ÿ—๏ธ Follow `modules` rule โ€” [specific rule, e.g. "add export at manifest end; use `using ..Sibling` for imports"] +> โš ๏ธ Follow `exceptions` rule โ€” [if stubs or error paths are added, e.g. "use `NotImplemented` with `required_method` and `suggestion` fields"] +> ๐Ÿ”ฌ Follow `type-stability` rule โ€” [if new parametric types or performance-critical functions are introduced] + +- <Bullet describing the first change in this file, naming exact symbols> +- <Bullet describing the second change> +- โ€ฆ + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched; + new stubs get a single `# TODO: docstring` comment only. + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched; +> new stubs get a single `# TODO: docstring` comment only. +``` + +Rules for step content: + +- **One file per step** when the change is non-trivial. Multiple small related files (e.g. a manifest + one tiny helper) may share a step if they are always changed together. +- **Name the exact symbols** being added, removed, or renamed โ€” no vague language like "update the code". +- **Specify signatures** for new functions: `function build_problem(int::AbstractIntegrator, sys, config; variable)`. +- **Specify struct fields** for new types: `struct SciMLIntegrationResult{S<:SciMLBase.AbstractODESolution}`, field `sol::S`. +- **Call out load-order changes** explicitly when a `using` or `include` order changes in a manifest. +- **Call out export changes**: which symbols are added to or removed from `export`. + +### 6. Test checkpoints (Interleaved) + +After a logical group of implementation steps, add a dedicated test checkpoint. Continue numbering. Plans should have multiple test checkpoints interspersed rather than one big block at the end. + +```markdown +### Step N โ€” Test Checkpoint: <Subsystem/Phase> + +> ๐Ÿงช Follow `testing-creation` rule โ€” [specific rule, e.g. "define all fake structs at module top-level; separate unit/integration/contract/error testsets"] +> ๐Ÿ”ฌ Follow `type-stability` rule โ€” [if type-stability tests are needed for new symbols] +> โ–ถ๏ธ Follow `testing-execution` rule โ€” run targeted tests for this phase. + +- Define `struct Fake<X> <: <AbstractType>` at module top-level in `test/suite/<subdir>/test_<name>.jl` (never inside test functions). +- Implement the required contract methods on the fake. +- Test sections: + - `@testset "Contract: NotImplemented errors"` โ€” verify stubs throw correctly. + - `@testset "Functional: <describe>"` โ€” verify behaviour with fakes. + - `@testset "Exports"` โ€” verify new exports are present, deleted symbols are gone. + - `@testset "Type Stability"` โ€” `@inferred` checks on new performance-critical functions (if applicable). +- Run targeted tests: + \`\`\`bash + julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/<subdir>"])' 2>&1 | tee /tmp/phase_N.log + \`\`\` +``` + +Rules for test steps: + +- Every new public symbol gets at least one test. +- Every deleted public symbol gets a regression test confirming it no longer exists. +- Fake types are defined at **module top-level**, never inside test functions. +- Test files are named `test_<name>.jl` and placed in the appropriate `test/suite/<subdir>/` directory. +- Use the test subdirectory map to determine where tests belong. + +### 7. Docstring step + +The final step (or final steps if large) writes all docstrings at once: + +```markdown +### Step N โ€” Docstrings + +> ๐Ÿ“š Follow `docstrings` rule โ€” use templates with `$(TYPEDEF)` and `$(TYPEDSIGNATURES)`, include required sections, use CTFlows-qualified references. + +- Add docstrings to all newly added public symbols in this phase. +- Replace `# TODO: docstring` comments with proper docstrings. +- Follow the CTFlows docstring conventions (full module paths like `CTFlows.Flows.build_flow`). +- Ensure cross-references use `[@ref]` for internal symbols and `[@extref]` for external symbols. +``` + +### 8. Verification step + +The final step runs the full test suite and checks coverage: + +```markdown +### Step N โ€” Verification + +> โ–ถ๏ธ Follow `testing-execution` rule โ€” run full test suite with coverage. + +- Run full test suite: + \`\`\`bash + julia --project -e 'using Pkg; Pkg.test("CTFlows")' 2>&1 | tee /tmp/verification.log + \`\`\` +- Check coverage if applicable. +- Review test output for failures. +``` + +### 9. Files summary + +List all files touched by the plan: + +```markdown +## Files Summary + +### New files +- `src/Flows/NewType.jl` +- `test/suite/flows/test_new_type.jl` + +### Modified files +- `src/Flows/Flows.jl` +- `src/Flows/building.jl` + +### Deleted files +- `src/Flows/OldType.jl` +``` + +## Naming Conventions + +### Branch names + +Use descriptive branch names: + +```bash +git checkout -b feature/add-hamiltonian-flows +git checkout -b refactor/extract-integrator-interface +git checkout -b fix/ode-integration-stability +``` + +### Test file names + +Test files are named `test_<name>.jl` where `<name>` describes what is being tested: + +```julia +test/suite/flows/test_flow.jl +test/suite/integrators/test_sciml_integrator.jl +test/suite/data/test_vector_field.jl +``` + +## Ordering Rules + +1. **Respect the module DAG**: steps touching lower-level modules (Common, Data) come before steps touching higher-level modules (Flows, Solutions). +2. **Manifests last within a module**: when editing a submodule, edit implementation files first, then update the `<Name>.jl` manifest at the end of that module's steps. +3. **Extensions after core**: any step touching `ext/` comes after all core steps are complete. +4. **Tests after implementation**: test checkpoints come after the implementation steps they validate. + +## Checklist + +Before finalizing a plan, verify: + +- [ ] Plan includes a clear title and one-paragraph summary +- [ ] "What changes and why" section is complete +- [ ] Dependency graph shows the new relationships +- [ ] Branch step uses `develop` as base +- [ ] Implementation steps are numbered starting from 1 +- [ ] Each step cites relevant rules at the point of use +- [ ] Steps respect the module DAG ordering +- [ ] Test checkpoints are interleaved (not just one at the end) +- [ ] Fake types are defined at module top-level in test steps +- [ ] Docstring step is separate and comes after all implementation +- [ ] Verification step runs the full test suite +- [ ] Files summary lists all new, modified, and deleted files +- [ ] No docstrings are written during implementation steps +- [ ] CTFlows-specific paths and names are used throughout + +## Related Rules + +- `.windsurf/rules/architecture.md` - Architecture and design principles +- `.windsurf/rules/modules.md` - Submodule conventions +- `.windsurf/rules/exceptions.md` - Exception handling standards +- `.windsurf/rules/docstrings.md` - Documentation standards +- `.windsurf/rules/testing-execution.md` - Test execution standards diff --git a/windsurf-rule-based/rules/testing-creation.md b/windsurf-rule-based/rules/testing-creation.md new file mode 100644 index 00000000..4fba115f --- /dev/null +++ b/windsurf-rule-based/rules/testing-creation.md @@ -0,0 +1,721 @@ +--- +trigger: glob +glob: "test/**/*.jl" +--- + +# Julia Testing Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿงช **Applying Testing Rule**: [specific testing principle being applied]" + +This ensures transparency about which testing standard is being used and why. + +--- + +This document defines the testing standards for the CTFlows.jl project. All Julia code modifications must be accompanied by appropriate tests following these guidelines. + +## Core Principles + +1. **Contract-First Testing**: Define and test contracts (interfaces) first using stubs/mocks to verify correct routing and behavior. Test both public APIs and internal functions when they implement important logic. +2. **Orthogonality**: Tests are independent from source code structure (test organization โ‰  src organization) +3. **Isolation**: Unit tests use mocks/fakes to isolate components; integration tests verify interactions +4. **Determinism**: Tests must be reproducible and not depend on external state +5. **Clarity**: Test intent must be immediately obvious from test names and structure + +## CTFlows Project Configuration + +### Package + +```julia +CTFlows +``` + +### Submodules + +| Submodule | Content | +| --- | --- | +| `Common` | `AbstractTag`, `AbstractTrait`, configs, traits, ODE parameters | +| `Data` | `AbstractVectorField`, `HamiltonianVectorField`, `VectorField` | +| `Systems` | `AbstractSystem` subtypes | +| `Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow`, building, calling | +| `Integrators` | `AbstractIntegrator`, `AbstractIntegrationResult`, building | +| `Solutions` | Solution building and accessors | + +### Import Style + +Use the `import X: X` qualified form so the submodule name is in scope: + +```julia +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Data: Data +import CTFlows.Systems: Systems +import CTFlows.Flows: Flows +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions +import CTSolvers.Strategies: Strategies +import CTSolvers.Options: Options +``` + +For extension test files that load SciML or StaticArrays, also add: + +```julia +using SciMLBase: SciMLBase, ODEProblem +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +import StaticArrays: SA +``` + +### Test Directory Structure + +```text +test/suite/ +โ”œโ”€โ”€ common/ # AbstractTag, AbstractTrait, configs, traits, ODE parameters +โ”œโ”€โ”€ data/ # AbstractVectorField, HamiltonianVectorField, VectorField +โ”œโ”€โ”€ flows/ # AbstractFlow, Flow, building, calling, callables +โ”œโ”€โ”€ integrators/ # AbstractIntegrator, building, SciML, IntegrationResult +โ”œโ”€โ”€ extensions/ # SciML, ForwardDiff, Plots, StaticArrays +โ”œโ”€โ”€ multiphase/ # MultiPhase flow tests (concatenation, calling) +โ”œโ”€โ”€ solutions/ # Solution building +โ”œโ”€โ”€ systems/ # AbstractSystem subtypes +โ””โ”€โ”€ meta/ # Aqua.jl quality checks +``` + +### Test Constants + +Every test file defines these constants at module level: + +```julia +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +``` + +### Test Entry Point Pattern + +```julia +# Inside the module +function test_<name>() + Test.@testset "<Description>" verbose=VERBOSE showtiming=SHOWTIMING begin + # โ€ฆ + end +end + +# CRITICAL: redefine in outer scope so the test runner can call it +test_<name>() = Test<Name>.test_<name>() +``` + +### Extension Access Pattern (for extension test files) + +```julia +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) +``` + +## Test Organization + +### Directory Structure + +Tests are organized under `test/suite/` by **functionality**, not by source file structure: + +- `suite/common/`: Common types tests (AbstractTag, AbstractTrait, configs, traits, ODE parameters) +- `suite/data/`: Data types tests (AbstractVectorField, HamiltonianVectorField, VectorField) +- `suite/flows/`: Flow types tests (AbstractFlow, Flow, building, calling, callables) +- `suite/integrators/`: Integrator tests (AbstractIntegrator, building, SciML, IntegrationResult) +- `suite/extensions/`: Extension tests (SciML, ForwardDiff, Plots, StaticArrays) +- `suite/multiphase/`: MultiPhase flow tests (concatenation, calling) +- `suite/solutions/`: Solution building tests +- `suite/systems/`: System types tests (AbstractSystem subtypes) +- `suite/meta/`: Meta tests (Aqua.jl quality checks) + +### File and Function Naming + +**Required pattern:** + +- File name: `test_<name>.jl` +- Entry function: `test_<name>()` (matching the filename exactly) + +**Example:** + +```julia +# File: test/suite/flows/test_abstract_flow.jl +module TestAbstractFlow + +import Test +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Data +import CTSolvers: CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_abstract_flow() + Test.@testset "Abstract Flow Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + # Tests here + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_flow() = TestAbstractFlow.test_abstract_flow() +``` + +## Test Categories + +**Vocabulary used in this document:** + +- **Fake** โ€” minimal struct that implements the required contract methods; used to isolate the component under test. +- **Stub** โ€” default method on an abstract type that throws `NotImplemented` or `ExtensionError`; tested by calling it on a fake type. +- **Mock** (interaction-recording) โ€” not used in CTFlows; fakes are sufficient. + +### 1. Unit Tests + +**Purpose**: Test single functions/components in isolation. + +**Characteristics:** + +- Pure logic, deterministic +- Use fake structs to isolate behavior +- No file I/O, network, or external dependencies +- Fast execution (<1ms per test) + +**Example:** + +```julia +Test.@testset "UNIT TESTS - Flow Types" begin + Test.@testset "Flow construction" begin + flow = Flows.Flow(fake_system, fake_integrator) + Test.@test flow isa Flows.Flow + Test.@test flow isa Flows.AbstractFlow + end +end +``` + +--- + +### 2. Integration Tests + +**Purpose**: Test interaction between multiple components through a complete workflow. + +**Characteristics:** + +- Exercise several real module boundaries together +- Use fakes only for leaf dependencies (not for the component chain being tested) +- Slower execution (acceptable up to 1s per test) + +**Example:** + +```julia +Test.@testset "INTEGRATION TESTS" begin + Test.@testset "flow building and system access" begin + sys = FakeSystem(2) # fake leaf dependency + integrator = FakeIntegrator() + + # build_flow exercises Flows + Systems + Integrators together + flow = Flows.build_flow(sys, integrator) + Test.@test flow isa Flows.AbstractFlow + Test.@test Flows.system(flow) isa Systems.AbstractSystem + Test.@test Flows.integrator(flow) isa Integrators.AbstractIntegrator + end +end +``` + +--- + +### 3. Contract Tests + +**Purpose**: Verify API contracts using fake implementations. + +**Characteristics:** + +- Define minimal fake types at top-level (never inside test functions) +- Implement only required contract methods +- Test routing, defaults, and error paths +- Verify Liskov Substitution Principle + +**Example:** + +```julia +# TOP-LEVEL: Fake type for contract testing +struct FakeSystem <: Systems.AbstractStateSystem{Common.Autonomous, Common.Fixed} + state_dim::Int +end + +# Implement contract +Systems.rhs(sys::FakeSystem) = (du, u, p, t) -> nothing +Systems.state_dimension(sys::FakeSystem) = sys.state_dim + +# Test contract +Test.@testset "Contract Implementation" begin + sys = FakeSystem(2) + Test.@test Systems.state_dimension(sys) == 2 +end +``` + +--- + +### 4. Error Tests + +**Purpose**: Verify that stubs and error paths throw the right exception with a useful message. + +Two sub-cases must be distinguished: + +#### 4a. Interface Stubs (`NotImplemented`) + +A fake that inherits from an abstract type but does **not** implement a required contract method will trigger the abstract type's stub, which throws `NotImplemented`. + +```julia +# TOP-LEVEL: fake that intentionally omits the required method +struct StubIntegrator <: Integrators.AbstractIntegrator end +# (no Integrators.integrate method defined for StubIntegrator) + +Test.@testset "Interface stubs" begin + Test.@testset "integrate throws NotImplemented" begin + stub = StubIntegrator() + Test.@test_throws Exceptions.NotImplemented Integrators.integrate(stub, sys, t0, x0, tf) + end +end +``` + +#### 4b. Extension Stubs (`ExtensionError`) + +Extension stubs throw `ExtensionError` when the required weak dependency is missing. **Always use a fake type** โ€” never a real type โ€” so that the test is independent of whether another test file happens to load the extension. + +```julia +# TOP-LEVEL: fake tag for which no extension registers an implementation +struct FakeExtTag <: Common.AbstractTag end + +Test.@testset "Extension stubs" begin + Test.@testset "unknown tag returns missing (fallback behavior)" begin + result = Integrators.__default_sciml_algorithm(FakeExtTag) + Test.@test result === missing + end +end +``` + +> **Why fake types?** If a real type is used and the extension is loaded (by any other test in the suite), the stub is replaced by the real implementation and the test silently passes or fails for the wrong reason. + +--- + +### Separation Pattern + +Use section comments to visually separate categories within a single testset: + +```julia +function test_flow_components() + Test.@testset "Flow Components" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + # Pure unit tests + end + + # ==================================================================== + # CONTRACT TESTS + # ==================================================================== + + Test.@testset "Contract Implementation" begin + # Contract tests with fakes + end + + # ==================================================================== + # INTEGRATION TESTS + # ==================================================================== + + Test.@testset "Integration" begin + # Multi-component tests + end + + # ==================================================================== + # ERROR TESTS + # ==================================================================== + + Test.@testset "Error Cases" begin + # Exception and edge-case tests + end + + end +end +``` + +## Critical Rules + +### Rule 1 โ€” Struct Definitions at Top-Level + +**NEVER define `struct`s inside test functions.** All helper types, mocks, and fakes must be defined at the **module top-level**. + +**โŒ Wrong:** + +```julia +function test_something() + Test.@testset "Test" begin + struct FakeType end # WRONG! Causes world-age issues + end +end +``` + +**โœ… Correct:** + +```julia +module TestSomething + +# TOP-LEVEL: Define all structs here +struct FakeType end + +function test_something() + Test.@testset "Test" begin + obj = FakeType() # Correct + end +end + +end # module +``` + +--- + +### Rule 2 โ€” Import and Qualification Rules + +**Use `import` instead of `using`** to avoid namespace pollution: + +```julia +import Test +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Data +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTSolvers: CTSolvers +``` + +**Always qualify method calls**, omitting the root module for submodules: + +**โœ… Correct:** + +```julia +Test.@test_throws Exceptions.IncorrectArgument invalid_call() +Test.@test Flows.system(flow) isa Systems.AbstractSystem +Test.@test Integrators.integrate(integrator, sys, t0, x0, tf) isa Integrators.AbstractIntegrationResult +``` + +**โŒ Wrong:** + +```julia +Test.@test_throws CTBase.Exceptions.IncorrectArgument invalid_call() # Too verbose +Test.@test CTFlows.Flows.system(flow) isa CTFlows.Systems.AbstractSystem # Too verbose +Test.@test true # Ambiguous +``` + +**Why:** Explicit qualification makes test intent clear while avoiding excessive verbosity. + +--- + +### Rule 3 โ€” Export Verification + +Add dedicated tests to verify exports and internal symbols: + +```julia +Test.@testset "Exports Verification" begin + Test.@testset "Submodule exports" begin + for sym in (:AbstractFlow, :Flow, :build_flow) + Test.@test isdefined(Flows, sym) + end + end + + Test.@testset "Package-level non-exports" begin + for sym in (:_internal_helper,) + Test.@test isdefined(Flows, sym) # exists in submodule + Test.@test !isdefined(CTFlows, sym) # not re-exported at package level + end + end +end +``` + +--- + +### Rule 4 โ€” Testing Internal Functions + +**Internal functions (prefixed with `_`) should be tested** when they contain significant logic. + +**Direct testing** โ€” preferred when logic is complex or has multiple branches: + +```julia +Test.@testset "Internal Function Tests" begin + result = Flows._validate_flow(flow, config) + Test.@test result isa Bool + Test.@test result == true +end +``` + +**Indirect testing** โ€” acceptable when logic is simple or already covered by integration tests: + +```julia +Test.@testset "build_flow - validation" begin + flow = Flows.build_flow(sys, integrator) + Test.@test Flows.system(flow) isa Systems.AbstractSystem +end +``` + +**When to test directly:** + +- Complex logic with multiple branches +- Error handling paths +- Edge cases hard to trigger via public API + +**When to test indirectly:** + +- Simple delegation or data transformation +- Logic already covered by integration tests +- Implementation details likely to change + +--- + +### Rule 5 โ€” Test Independence + +Each test must be independent and not rely on execution order. Create fresh instances inside each testset: + +**โœ… Correct:** + +```julia +Test.@testset "Test A" begin + flow = Flows.build_flow(FakeSystem(2), FakeIntegrator()) + # Test A logic +end + +Test.@testset "Test B" begin + flow = Flows.build_flow(FakeSystem(2), FakeIntegrator()) # Fresh instance + # Test B logic +end +``` + +**โŒ Wrong:** + +```julia +flow = Flows.build_flow(...) # Shared state + +Test.@testset "Test A" begin + # Modifies flow โ€” affects Test B! +end +``` + +--- + +### Rule 6 โ€” Stub Testing + +Two kinds of stubs exist in CTFlows; each requires a different approach. + +#### 6a. Interface Stubs (`NotImplemented`) + +An abstract type's default method throws `NotImplemented` when a concrete subtype has not provided an implementation. Test this by creating a **fake that omits the required method**. + +```julia +# TOP-LEVEL: fake that deliberately does NOT implement integrate +struct StubIntegrator <: Integrators.AbstractIntegrator end + +Test.@testset "Interface stubs" begin + Test.@testset "integrate stub throws NotImplemented" begin + stub = StubIntegrator() + Test.@test_throws Exceptions.NotImplemented Integrators.integrate(stub, sys, t0, x0, tf) + end +end +``` + +This test is safe regardless of which extensions are loaded. + +#### 6b. Extension Stubs (`ExtensionError` / fallback behavior) + +Extension code registers implementations for **known** types. When called with an **unknown** type, the stub (fallback) returns `missing` or throws `ExtensionError`. **Always use a fake type** that no extension knows about. + +**Why:** If a real type is used (e.g., `Integrators.SciML`) and the extension is loaded by another test file, the stub is replaced by the real implementation โ€” the test silently passes or fails for the wrong reason. + +```julia +# TOP-LEVEL: fake tag โ€” no extension registers an impl for this +struct FakeExtTag <: Common.AbstractTag end + +Test.@testset "Extension stubs" begin + Test.@testset "unknown tag โ†’ fallback returns missing" begin + result = Integrators.__default_sciml_algorithm(FakeExtTag) + Test.@test result === missing + end +end +``` + +**โŒ Wrong โ€” real type, load-order dependent:** + +```julia +# FAILS when CTFlowsSciML is loaded elsewhere in the suite +Test.@test_throws Exceptions.ExtensionError Integrators.SciML() +``` + +**Allowed with real types:** + +- Type hierarchy checks: `Test.@test Integrators.SciMLIntegrator <: Integrators.AbstractIntegrator` +- Pure metadata methods that don't depend on extension state (`id`, `description`) + +--- + +## Test Quality Standards + +### Assertion Quality + +**Use specific assertions:** + +**โœ… Good:** + +```julia +Test.@test result โ‰ˆ 1.23 atol=1e-10 +Test.@test obj isa Systems.AbstractSystem +Test.@test length(components) == 2 +Test.@test status == :first_order +``` + +**โŒ Poor:** + +```julia +Test.@test result > 0 # Too vague +Test.@test obj != nothing # Use Test.@test !isnothing(obj) +Test.@test true # Meaningless +``` + +### Test Naming + +Test names should describe **what** is being tested, not **how**: + +**โœ… Good:** + +```julia +Test.@testset "System construction" +Test.@testset "Contract Implementation - NotImplemented errors" +Test.@testset "Complete workflow - flow building" +``` + +**โŒ Poor:** + +```julia +Test.@testset "Test 1" +Test.@testset "Builder" +Test.@testset "Check stuff" +``` + +## Coverage Requirements + +### What to Test + +**Must test:** + +- โœ… Public API functions and types +- โœ… Contract implementations +- โœ… Error paths and exception handling +- โœ… Edge cases (empty inputs, boundary values, special cases) +- โœ… Type stability (for performance-critical code) +- โœ… Integration between components + +**Should test:** + +- โš ๏ธ Internal functions with complex logic +- โš ๏ธ Validation logic +- โš ๏ธ Conversion and transformation functions + +**Don't test:** + +- โŒ Trivial getters/setters without logic +- โŒ External library behavior +- โŒ Generated code (unless custom logic added) + +### Performance and Type Stability Tests + +For performance-critical code, add type stability and allocation tests. + +**See also:** `type-stability` rule for comprehensive standards. + +#### Type Stability Tests + +`@inferred` only works on **function calls**, not direct field access: + +```julia +Test.@testset "Type Stability" begin + sys = FakeSystem(2) + flow = build_test_flow(sys) # helper defined at module top-level + + Test.@test_nowarn Test.@inferred Flows.system(flow) + Test.@test_nowarn Test.@inferred Integrators.integrate(integrator, sys, t0, x0, tf) + + # โŒ WRONG: @inferred on field access + # Test.@inferred flow.system # ERROR! + + # โœ… CORRECT: wrap in a function call + Test.@test_nowarn Test.@inferred Flows.system(flow) +end +``` + +#### Allocation Tests + +```julia +Test.@testset "Allocations" begin + sys = FakeSystem(2) + allocs = Test.@allocated Systems.state_dimension(sys) + Test.@test allocs == 0 +end +``` + +## Anti-Patterns to Avoid + +### โŒ Don't: Test implementation details + +```julia +# BAD: Testing internal field names +Test.@test obj._internal_cache == something +``` + +### โŒ Don't: Use global mutable state + +```julia +# BAD: Global state between tests +const GLOBAL_COUNTER = Ref(0) + +Test.@testset "Test A" begin + GLOBAL_COUNTER[] += 1 # Affects other tests! +end +``` + +### โŒ Don't: Depend on test execution order + +```julia +# BAD: Test B depends on Test A running first +Test.@testset "Test A" begin + global shared_data = compute_something() +end + +Test.@testset "Test B" begin + Test.@test shared_data > 0 # Breaks if A doesn't run first! +end +``` + +## Quality Checklist + +Before finalizing tests, verify: + +- [ ] All structs defined at module top-level +- [ ] Unit and integration tests clearly separated +- [ ] Method calls are qualified (e.g., `Systems.function_name`) +- [ ] Test names describe what is being tested +- [ ] Each test is independent and deterministic +- [ ] Error cases are tested with `@test_throws` +- [ ] No file I/O or external dependencies in unit tests +- [ ] Fake types implement minimal contracts +- [ ] Tests document non-obvious logic +- [ ] No global mutable state +- [ ] Tests pass locally before committing + +## References + +- Test execution: `testing-execution` rule (`.windsurf/rules/testing-execution.md`) +- Test runner entry point: `test/runtests.jl` diff --git a/windsurf-rule-based/rules/testing-execution.md b/windsurf-rule-based/rules/testing-execution.md new file mode 100644 index 00000000..64f5bdc0 --- /dev/null +++ b/windsurf-rule-based/rules/testing-execution.md @@ -0,0 +1,104 @@ +--- +trigger: model_decision +--- + +# Julia Test Execution Guide + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿงช **Applying Testing Rule**: [specific testing principle being applied]" + +This ensures transparency about which testing standard is being used and why. + +--- + +This document defines how to run tests for the CTFlows.jl project. For test creation standards, see `testing.md`. + +## Running Tests + +For detailed instructions on how to run tests (specific tests, test groups, or all tests), please refer to the testing guide: + +**See**: `test/README.md` + +This file contains comprehensive information about: + +- Running all enabled tests +- Running specific test groups using glob patterns +- Running individual test files +- Running all tests including optional/long tests +- Generating coverage reports + +### Capturing Test Output (Agents) + +When running tests from the terminal (especially AI agents), **always pipe the output to a file via `tee`** instead of truncating with `tail -N`. Truncated output frequently hides the first failure or the compilation errors that trigger subsequent issues, forcing a second run. + +**โœ… Good โ€” capture full log, inspect tail afterwards:** + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems/test_abstract_system"])' \ + 2>&1 | tee /tmp/ctflows_test.log +# then, if needed, grep/tail the saved log without rerunning: +grep -E "Error|Fail|Test Summary" /tmp/ctflows_test.log +tail -200 /tmp/ctflows_test.log +``` + +**โŒ Avoid โ€” truncated stream lost if you need more context:** + +```bash +julia --project -e '...' 2>&1 | tail -120 # if the relevant error is above line -120, you must rerun +``` + +**Rules of thumb:** + +- Save to `/tmp/<pkg>_<scope>.log` (e.g., `/tmp/ctflows_test_systems.log`) so multiple concurrent sessions do not collide. +- Prefer `tee` on the full run; then use `grep`, `rg`, `tail`, or `less` on the saved file. +- Clean up `/tmp/*.log` files periodically; do not commit them. + +## Quick Test Commands + +### Convenience Alias: `jtest` + +If the `jtest` alias is defined in your shell (typically in `~/.zsh_aliases`), you can use it as a shortcut: + +```bash +# Alias definition (in ~/.zsh_aliases) +alias jtest="/Users/ocots/bin/julia_test.sh" +``` + +The `jtest` script wraps `Pkg.test()`: + +```bash +# Run all tests +jtest + +# Run specific test suite +jtest suite/systems/test_abstract_system +``` + +**Script location**: `~/bin/julia_test.sh` (user-local). If you have this alias, it provides a convenient way to run tests without typing the full Julia invocation each time. + +### Run all tests + +```bash +julia --project=@. -e 'using Pkg; Pkg.test()' +``` + +### Run specific test group + +```bash +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["ocp"])' +``` + +### Generate coverage report + +```bash +julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; coverage=true); include("test/coverage.jl")' +``` + +## References + +- Test README: `test/README.md` +- Test creation standards: `testing.md` +- Test workflows: `@/test-julia`, `@/test-julia-debug` +- Shared test problems: `test/problems/TestProblems.jl` +- Test runner: Uses `CTBase.TestRunner` extension diff --git a/windsurf-rule-based/rules/type-stability.md b/windsurf-rule-based/rules/type-stability.md new file mode 100644 index 00000000..54695506 --- /dev/null +++ b/windsurf-rule-based/rules/type-stability.md @@ -0,0 +1,291 @@ +--- +trigger: model_decision +--- + +# Julia Type Stability Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ”ง **Applying Type Stability Rule**: [specific type stability principle being applied]" + +This ensures transparency about which type stability standard is being used and why. + +--- + +This document defines type stability standards for the Control Toolbox project. Type stability is crucial for Julia performance and must be carefully considered in performance-critical code paths. + +## Core Principles + +1. **Type Inference**: The compiler must be able to determine return types from input types +2. **Performance**: Type-stable code is typically 10-100x faster than type-unstable code +3. **Testability**: Type stability must be verified with `@inferred` tests +4. **Clarity**: Type-stable code is often clearer and more maintainable + +## What is Type Stability? + +A function is **type-stable** if the type of its return value can be inferred from the types of its inputs at compile time. + +```julia +# โœ… Type-stable: return type is always Int +function get_dimension(ocp::OptimalControlProblem)::Int + return ocp.state_dimension +end + +# โŒ Type-unstable: return type depends on runtime value +function get_value(dict::Dict{Symbol, Any}, key::Symbol) + return dict[key] # Could be Int, Float64, String, anything! +end +``` + +## Testing Type Stability + +### Using `@inferred` + +```julia +@testset "Type Stability" begin + ocp = create_test_ocp() + + @test_nowarn @inferred get_dimension(ocp) + @test_nowarn @inferred state_dimension(ocp) + @test_nowarn @inferred process_constraint(ocp, :initial) +end +``` + +### Common Mistake: Testing Non-Functions + +```julia +# โŒ WRONG: @inferred on field access +@inferred ocp.state_dimension # ERROR: Not a function call! + +# โœ… CORRECT: Wrap in a function +function get_state_dim(ocp) + return ocp.state_dimension +end +@inferred get_state_dim(ocp) +``` + +### Using `@code_warntype` + +```julia +julia> @code_warntype get_value(dict, :key) +# Look for red "Any" or yellow warnings in the output +``` + +**What to look for:** +- Red `Any` or `Union{...}` in return type +- Yellow warnings about type instabilities +- Multiple possible return types + +## Type-Stable Structures + +### Use Parametric Types + +**โŒ Type-Unstable:** + +```julia +struct OptionDefinition + name::Symbol + type::Type + default::Any # Type-unstable! +end +``` + +**โœ… Type-Stable:** + +```julia +struct OptionDefinition{T} + name::Symbol + type::Type{T} + default::T # Type-stable! +end + +function get_default(opt::OptionDefinition{T}) where T + return opt.default # Return type: T +end +``` + +### Use NamedTuple Instead of Dict + +**โŒ Type-Unstable:** + +```julia +struct StrategyMetadata + specs::Dict{Symbol, OptionDefinition} # Values have unknown types +end +``` + +**โœ… Type-Stable:** + +```julia +struct StrategyMetadata{NT <: NamedTuple} + specs::NT # Type-stable with known keys +end +``` + +### Avoid Abstract Types in Structs + +**โŒ Type-Unstable:** + +```julia +struct Container + items::Vector{Number} # Abstract type! +end +``` + +**โœ… Type-Stable:** + +```julia +struct Container{T <: Number} + items::Vector{T} # Concrete type parameter +end +``` + +## Common Type Instabilities + +### 1. Untyped Containers + +```julia +# โŒ Type-unstable +results = [] # Vector{Any} + +# โœ… Type-stable +results = Int[] +``` + +### 2. Conditional Return Types + +```julia +# โŒ Type-unstable: Union{Int, Nothing} +function get_value(x::Int) + if x > 0 + return x + else + return nothing + end +end + +# โœ… Type-stable +function get_value(x::Int)::Int + return x > 0 ? x : 0 +end +``` + +### 3. Global Variables + +```julia +# โŒ Type-unstable +global_counter = 0 + +# โœ… Type-stable +const GLOBAL_COUNTER = Ref(0) +``` + +### 4. Type-Unstable Fields + +```julia +# โŒ Type-unstable +mutable struct Cache + data::Any +end + +# โœ… Type-stable +mutable struct Cache{T} + data::T +end +``` + +## Fixing Type Instabilities + +### Strategy 1: Add Type Annotations + +```julia +function process(x::Vector{Float64}) + result = Float64[] + # ... +end +``` + +### Strategy 2: Use Function Barriers + +```julia +# Type-unstable outer function +function outer(data::Dict{Symbol, Any}) + value = data[:key] # Type-unstable + return inner(value) # Function barrier isolates instability +end + +# Type-stable inner function +function inner(value::Int) + return value^2 +end +``` + +### Strategy 3: Parametric Types + +```julia +# Before +struct Container + data::Vector{Any} +end + +# After +struct Container{T} + data::Vector{T} +end +``` + +## When Type Stability Matters + +### Critical Paths (must be type-stable) + +- Inner loops (called millions of times) +- Hot paths in solvers +- Numerical computations +- Real-time systems + +### Less Critical Paths + +- One-time setup code +- User-facing API layers +- Error handling paths +- Debugging utilities + +## Performance Testing + +```julia +@testset "Allocations" begin + ocp = create_test_ocp() + + allocs = @allocated state_dimension(ocp) + @test allocs == 0 + + allocs = @allocated build_model(ocp) + @test allocs < 1000 # bytes +end +``` + +## Quality Checklist + +Before finalizing code, verify: + +- [ ] Critical functions tested with `@inferred` +- [ ] No `Any` types in hot paths +- [ ] Parametric types used where appropriate +- [ ] `@code_warntype` shows no red flags +- [ ] Allocation tests pass for critical operations +- [ ] Benchmarks meet performance targets + +## Key Takeaways + +1. Type stability is crucial for Julia performance +2. Test with `@inferred` for all critical functions +3. Use parametric types and NamedTuple for type-stable structures +4. Avoid `Any` and abstract types in hot paths +5. Use `@code_warntype` to debug instabilities +6. Test allocations for performance-critical code + +## Related Skills + +- `performance` rule โ€” profiling, benchmarking, allocation reduction +- `testing-creation` rule โ€” type stability test patterns +- `architecture` rule โ€” parametric type design diff --git a/windsurf-skill-based/rules/documentation.md b/windsurf-skill-based/rules/documentation.md new file mode 100644 index 00000000..0a52ab21 --- /dev/null +++ b/windsurf-skill-based/rules/documentation.md @@ -0,0 +1,464 @@ +--- +trigger: glob +glob: "docs/**/*" +--- + +# Julia API Documentation Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ“– **Applying Documentation Rule**: [specific documentation principle being applied]" + +This ensures transparency about which documentation principle is being used and why. + +--- + +This document defines how the `docs/` directory of a Control Toolbox package is organised and built. It complements [`docstrings.md`](docstrings.md) (which covers *what* to write inside docstrings) by specifying *how* those docstrings are turned into a published documentation site via [Documenter.jl](https://documenter.juliadocs.org/) and [`CTBase.automatic_reference_documentation()`](https://control-toolbox.org/CTBase.jl/stable/guide/api-documentation.html). + +## Reference Implementations + +Three control-toolbox packages illustrate the spectrum of complexity. All three share the same skeleton; differences are stylistic. + +| Package | Scale | Notable features | +| --- | --- | --- | +| [`CTSolvers.jl/docs`](https://github.com/control-toolbox/CTSolvers.jl/tree/main/docs) | mid-weight package | Architecture page + Developer Guides + API Reference; `subdirectory="api"`; DocumenterMermaid | +| [`CTModels.jl/docs`](https://github.com/control-toolbox/CTModels.jl/tree/main/docs) | single package | Introduction + API Reference only; `subdirectory="."`; multiple extensions with `DocMeta.setdocmeta!`; Q&A-style Quick Start | +| [`OptimalControl.jl/docs`](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs) | meta-package | `DocumenterInterLinks` for cross-refs to dependencies; copies `Project.toml`/`Manifest.toml` to assets; documents only its Private API (Public is exposed via `@extref`) | + +## Core Principles + +1. **Auto-generated API reference** โ€” never hand-write API pages; use `CTBase.automatic_reference_documentation()`. +2. **One page per submodule** โ€” each submodule gets its own auto-generated API page. +3. **One page per loaded extension** โ€” each `Base.get_extension`-detected extension gets its own page when present. +4. **Public + private documented** โ€” both `public=true` and `private=true`, since users access internals via qualified paths (consistent with [`modules.md`](modules.md)). +5. **Hand-written guides separate from API** โ€” narrative guides live under `docs/src/guides/`; the API reference is generated. +6. **Index page is the entry point** โ€” `docs/src/index.md` provides admonitions, module table, guide links via `[@ref]`, and a Quick Start. +7. **Cross-references resolve at build time** โ€” every `[@extref]` in a docstring must be backed by an `InterLinks` entry in `make.jl`. + +## Directory Layout + +```text +docs/ +โ”œโ”€โ”€ Project.toml +โ”œโ”€โ”€ make.jl # entry point; uses with_api_reference() +โ”œโ”€โ”€ api_reference.jl # generate_api_reference() + with_api_reference() +โ”œโ”€โ”€ inventories/ # InterLinks fallback inventories (one per dependency) +โ”‚ โ”œโ”€โ”€ CTBase.toml +โ”‚ โ”œโ”€โ”€ CTModels.toml +โ”‚ โ””โ”€โ”€ CTSolvers.toml +โ””โ”€โ”€ src/ + โ”œโ”€โ”€ index.md # landing page + โ”œโ”€โ”€ architecture.md # narrative architecture page (optional) + โ”œโ”€โ”€ guides/ # hand-written guides + โ”‚ โ”œโ”€โ”€ implementing_a_modeler.md + โ”‚ โ”œโ”€โ”€ implementing_an_integrator.md + โ”‚ โ””โ”€โ”€ ... + โ””โ”€โ”€ api/ # auto-generated (cleaned up after build) +``` + +## Cross-Reference Infrastructure: DocumenterInterLinks + +For the `[@extref]` syntax (defined in [`docstrings.md`](docstrings.md)) to actually resolve at build time, `make.jl` must declare an `InterLinks` registry โ€” one entry per cross-referenced dependency: + +```julia +using DocumenterInterLinks + +links = InterLinks( + "CTBase" => ( + "https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml"), + ), + "CTModels" => ( + "https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml"), + ), + "CTSolvers" => ( + "https://control-toolbox.org/CTSolvers.jl/stable/", + "https://control-toolbox.org/CTSolvers.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTSolvers.toml"), + ), + # โ€ฆ one entry per dependency referenced via @extref +) +``` + +Each entry is a 3-tuple: stable docs URL, `objects.inv` URL (Sphinx-style inventory served by Documenter.jl), and a local TOML inventory under `docs/inventories/` used as fallback. Pass `links` to `makedocs` via the `plugins` argument: + +```julia +makedocs(; plugins=[links], ...) +``` + +This is what makes references like `` [`CTSolvers.Strategies.AbstractStrategy`](@extref) `` resolve to the dependency's published documentation. + +## `docs/make.jl` Template + +### Common skeleton + +```julia +pushfirst!(LOAD_PATH, joinpath(@__DIR__)) +pushfirst!(LOAD_PATH, joinpath(@__DIR__, "..")) + +using Documenter +using DocumenterInterLinks +using CTFlows +using CTBase +using Markdown +using MarkdownAST: MarkdownAST +# Optional: using DocumenterMermaid + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DocumenterReference extension (from CTBase) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) +if !isnothing(DocumenterReference) + DocumenterReference.reset_config!() +end + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DocMeta setup for the package and its extensions (loop pattern) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +Modules = [CTFlows, CTFlowsODE] # add extensions and dependencies as needed +for Module in Modules + isnothing(Module) && continue + isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && + DocMeta.setdocmeta!(Module, :DocTestSetup, :(using $Module); recursive=true) +end + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# InterLinks (only if @extref is used in docstrings) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +links = InterLinks( + "CTBase" => ("https://control-toolbox.org/CTBase.jl/stable/", + "https://control-toolbox.org/CTBase.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTBase.toml")), + "CTModels" => ("https://control-toolbox.org/CTModels.jl/stable/", + "https://control-toolbox.org/CTModels.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTModels.toml")), + "CTSolvers" => ("https://control-toolbox.org/CTSolvers.jl/stable/", + "https://control-toolbox.org/CTSolvers.jl/stable/objects.inv", + joinpath(@__DIR__, "inventories", "CTSolvers.toml")), +) + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Paths and API reference generator +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +repo_url = "github.com/control-toolbox/CTFlows.jl" +src_dir = abspath(joinpath(@__DIR__, "..", "src")) +ext_dir = abspath(joinpath(@__DIR__, "..", "ext")) + +include("api_reference.jl") + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Build +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +with_api_reference(src_dir, ext_dir) do api_pages + makedocs(; + draft = false, + remotes = nothing, + warnonly = [:cross_references], + sitename = "CTFlows.jl", + plugins = [links], + format = Documenter.HTML(; + repolink = "https://" * repo_url, + prettyurls = false, + assets = [ + asset("https://control-toolbox.org/assets/css/documentation.css"), + asset("https://control-toolbox.org/assets/js/documentation.js"), + ], + ), + pages = [ + "Introduction" => "index.md", + "Architecture" => "architecture.md", + "Developer Guides" => [ + "Implementing a System" => "guides/implementing_a_system.md", + "Implementing a Flow Modeler" => "guides/implementing_a_flow_modeler.md", + "Implementing an ODE Integrator" => "guides/implementing_an_ode_integrator.md", + "Implementing an AD Backend" => "guides/implementing_an_ad_backend.md", + "Pipelines" => "guides/pipelines.md", + "Multi-phase Composition" => "guides/multi_phase_composition.md", + "Error Messages Reference" => "guides/error_messages.md", + ], + "API Reference" => api_pages, + ], + ) +end + +deploydocs(; repo=repo_url * ".git", devbranch="main") +``` + +### Variations + +`pages` structure: + +- **Light (CTModels-style)** โ€” `pages = ["Introduction" => "index.md", "API Reference" => api_pages]`. Use when there are no narrative guides. +- **Full (CTSolvers-style, recommended for CTFlows)** โ€” Architecture + guides + API Reference, as shown above. + +`warnonly` setting: + +- `warnonly = true` (CTSolvers) โ€” accept all build warnings. +- `warnonly = [:cross_references]` (CTModels, recommended for CTFlows) โ€” accept only cross-reference warnings. + +`prettyurls`: + +- `false` for local browsing during development. +- `true` for deployed documentation (omit or rely on default). + +## `docs/api_reference.jl` Template + +The file defines two public functions and one internal helper: + +- `generate_api_reference(src_dir, ext_dir) -> pages` โ€” builds the `pages` vector by calling `CTBase.automatic_reference_documentation` for each submodule and each loaded extension. +- `with_api_reference(f, src_dir, ext_dir)` โ€” wrapper that generates pages, calls `f(pages)`, then cleans up generated `.md` files via `_cleanup_pages` (in a `try/finally`). +- `_cleanup_pages(docs_src, pages)` โ€” recursive helper that deletes the auto-generated files after the build. + +### Submodule call + +```julia +CTBase.automatic_reference_documentation(; + subdirectory = "api", # "api" (CTSolvers) or "." (CTModels) + primary_modules = [ + CTFlows.Systems => src( + joinpath("Systems", "Systems.jl"), + joinpath("Systems", "abstract_system.jl"), + # ... all included files of the submodule + ), + ], + external_modules_to_document = [CTFlows], # include re-exported symbols + exclude = EXCLUDE_SYMBOLS, + public = true, + private = true, + title = "Systems", + title_in_menu = "Systems", + filename = "api_systems", # "api_*" (CTModels) or "*" (CTSolvers) +) +``` + +### Extension call (auto-detected) + +```julia +CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) +if !isnothing(CTFlowsODE) + push!(pages, + CTBase.automatic_reference_documentation(; + subdirectory = "api", + primary_modules = [CTFlowsODE => ext("CTFlowsODE.jl")], + external_modules_to_document = [CTFlows], + exclude = EXCLUDE_SYMBOLS, + public = true, + private = true, + title = "ODE Extension", + title_in_menu = "ODE", + filename = "api_ext_ode", + ), + ) +end +``` + +### Variations on the API call + +- **`subdirectory`** โ€” `"api"` puts pages under `docs/src/api/`; `"."` puts them directly under `docs/src/`. Pick one and stay consistent. +- **`filename` prefix** โ€” `"api_systems"` (CTModels) makes auto-generated files visually distinct from hand-written ones; `"systems"` (CTSolvers) is fine when pages live under `api/`. +- **`external_modules_to_document`** โ€” set to `[CTFlows]` whenever a submodule re-exports symbols at package level (almost always). +- **`EXCLUDE_SYMBOLS`** โ€” start from `Symbol[:include, :eval]` and extend with package-specific noise (private macros, helper symbols leaked from `using`). + +### Cleanup helper + +```julia +function _cleanup_pages(docs_src::String, pages) + for p in pages + val = last(p) + if val isa AbstractString + fname = endswith(val, ".md") ? val : val * ".md" + full_path = joinpath(docs_src, fname) + if isfile(full_path) + rm(full_path) + println("Removed temporary API doc: $full_path") + end + elseif val isa AbstractVector + _cleanup_pages(docs_src, val) + end + end +end +``` + +### Meta-package variant (OptimalControl-style) + +When the package re-exports symbols from several control-toolbox dependencies, the public API is documented via `[@extref]` to those dependencies; the package itself only documents its **private** API: + +```julia +pages = [ + CTBase.automatic_reference_documentation(; + subdirectory = "api", + primary_modules = [ + OptimalControl => src(joinpath("helpers", "..."), ...) + ], + external_modules_to_document = [CTBase, CTModels, CTSolvers], + public = false, # public API lives in the dependencies + private = true, + title = "Private", + title_in_menu = "Private", + filename = "private", + ), +] +``` + +For CTFlows (single package), this variant does not apply โ€” it is documented here for completeness. + +### Optional: copy `Project.toml` / `Manifest.toml` to assets + +For packages where users may want to reproduce the exact documentation environment (typically the meta-package): + +```julia +mkpath(joinpath(@__DIR__, "src", "assets")) +cp(joinpath(@__DIR__, "Project.toml"), + joinpath(@__DIR__, "src", "assets", "Project.toml"); force=true) +cp(joinpath(@__DIR__, "Manifest.toml"), + joinpath(@__DIR__, "src", "assets", "Manifest.toml"); force=true) +``` + +Place these `cp` calls in `make.jl`, before `makedocs`. + +## `docs/src/index.md` Template + +Mandatory structure โ€” the *end* of the file (Documentation section + Quick Start) is what users land on first: + +````markdown +# CTFlows.jl + +```@meta +CurrentModule = CTFlows +``` + +The `CTFlows.jl` package is part of the [control-toolbox ecosystem](https://github.com/control-toolbox). +It provides the **flow layer** for optimal control problems: + +- **Systems** โ€” assembled callable objects (`AbstractSystem`) +- **Flows** โ€” system + integrator pairs (`AbstractFlow`) +- **Modelers** โ€” flow modeler strategies (`AbstractFlowModeler`) +- **Integrators** โ€” ODE integrator strategies (`AbstractODEIntegrator`) +- **AD Backends** โ€” automatic-differentiation strategies (`AbstractADBackend`) +- **Pipelines** โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` + +!!! info "CTFlows vs CTModels and CTSolvers" + **CTFlows** focuses on **flowing** dynamical systems associated with optimal control problems + (assembling systems, integrating ODEs, building solutions). + For **defining** the problems themselves, see [CTModels.jl](https://github.com/control-toolbox/CTModels.jl); + for **solving** them via discretisation and NLP, see [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl). + +!!! note + The root package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl) which aims + to provide tools to model and solve optimal control problems with ordinary differential equations + by direct and indirect methods, both on CPU and GPU. + +!!! warning "Qualified Module Access" + CTFlows does **not** export functions at the package level. All functions and types are + accessed via qualified module paths (consistent with the [submodule architecture](modules.md)): + + ```julia + using CTFlows + CTFlows.Systems.dimensions(sys) # โœ“ Qualified + CTFlows.Pipelines.build_system(input, m, ad) # โœ“ Qualified + ``` + +## Modules + +| Module | Purpose | +|--------|---------| +| `Core` | Shared types and utilities | +| `Systems` | `AbstractSystem`, concrete systems, `MultiPhaseSystem` | +| `Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow` | +| `Modelers` | `AbstractFlowModeler` and concrete modelers | +| `Integrators` | `AbstractODEIntegrator` and concrete integrators | +| `ADBackends` | `AbstractADBackend` and concrete backends | +| `Pipelines` | `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` | + +## Documentation + +### Developer Guides + +- [Architecture](@ref) โ€” module overview, type hierarchy, data flow +- [Implementing a System](@ref) โ€” `AbstractSystem` contract +- [Implementing a Flow Modeler](@ref) โ€” `AbstractFlowModeler` strategy +- [Implementing an ODE Integrator](@ref) โ€” `AbstractODEIntegrator` strategy +- [Implementing an AD Backend](@ref) โ€” `AbstractADBackend` strategy +- [Pipelines](@ref) โ€” `build_system`, `build_flow`, `integrate`, `build_solution`, `solve` +- [Multi-phase Composition](@ref) โ€” `MultiPhaseSystem` and `MultiPhaseFlow` +- [Error Messages Reference](@ref) โ€” exception types with examples and fixes + +### API Reference + +Auto-generated documentation for all public and private symbols, organised by submodule. + +## Quick Start + +```julia +using CTFlows +using OrdinaryDiffEq # loads the ODE integration extension + +# Build a system from an OCP +sys = CTFlows.Pipelines.build_system((ocp, u), modeler, ad_backend) + +# Build a flow (system + integrator) +flow = CTFlows.Pipelines.build_flow(sys, integrator) + +# Integrate +sol = CTFlows.Pipelines.solve(flow, (t0, tf), x0, p0) +``` +```` + +### Quick Start variants + +- **Code-first (CTSolvers-style, shown above)** โ€” a short, runnable Julia code block illustrating typical usage with qualified paths. +- **Q&A (CTModels-style)** โ€” a list of "I want to ..." entries, each pointing to the relevant API page or guide. Useful when the API surface is wide. + +## Conventions + +### Admonitions + +| Type | Use | +| --- | --- | +| `!!! info` | Contrasting the package with siblings, scope statements | +| `!!! note` | Pointers to the root package or related material | +| `!!! warning` | Qualified-access policy, breaking caveats | +| `!!! tip` | Performance hints, idiomatic patterns | + +### Cross-references + +- `[Title](@ref)` โ€” in-package references (resolves to a heading or docstring in the current docs). +- `` [`Pkg.Submodule.sym`](@extref) `` โ€” references to symbols in a dependency with separate documentation. Requires the dependency to appear in `InterLinks`. + +See [`docstrings.md`](docstrings.md) for the full cross-reference policy. + +### Code examples + +Prefer fully qualified calls in examples (`CTFlows.Systems.dimensions(sys)`) โ€” consistent with [`modules.md`](modules.md). Exceptions: short snippets where the qualified form would obscure the point and the symbol is unambiguous. + +## Build Commands + +### Local build + +```bash +julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate(); include("docs/make.jl")' +``` + +### CI deployment + +Handled by `deploydocs(; repo=repo_url * ".git", devbranch="main")` at the bottom of `make.jl`. The standard control-toolbox GitHub Actions workflow takes care of the rest. + +## Quality Checklist + +Before finalising the documentation setup, verify: + +- [ ] `docs/make.jl` uses the `with_api_reference()` wrapper. +- [ ] `docs/api_reference.jl` exists with `generate_api_reference`, `with_api_reference`, and `_cleanup_pages` functions. +- [ ] `DocumenterReference` extension is loaded and reset via `reset_config!()`. +- [ ] If `[@extref]` is used in any docstring, `DocumenterInterLinks` is set up in `make.jl` with one `InterLinks` entry per cross-referenced dependency, and `links` is passed to `makedocs(; plugins=[links])`. +- [ ] One `automatic_reference_documentation` call per submodule, both `public=true` and `private=true`, with `external_modules_to_document=[CTFlows]` when relevant. +- [ ] Each known extension is detected via `Base.get_extension` and conditionally documented. +- [ ] `DocMeta.setdocmeta!` loop covers the package and its loaded extensions when doctests are used. +- [ ] `docs/src/index.md` contains: meta block, ecosystem link, info/note/warning admonitions, modules table, guide links via `[@ref]`, API reference note, and Quick Start. +- [ ] All cross-references use `[@ref]` / `[@extref]` correctly (see [`docstrings.md`](docstrings.md)). +- [ ] Hand-written guides are placed under `docs/src/guides/`. +- [ ] No hand-written API pages โ€” everything in `api/` is generated and cleaned up by `_cleanup_pages`. diff --git a/windsurf-skill-based/rules/modules.md b/windsurf-skill-based/rules/modules.md new file mode 100644 index 00000000..1bbd1278 --- /dev/null +++ b/windsurf-skill-based/rules/modules.md @@ -0,0 +1,350 @@ +--- +trigger: glob +glob: "src/**/*.jl, ext/**/*.jl" +--- + +# Julia Submodule Architecture Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿ—๏ธ **Applying Modules Rule**: [specific principle being applied]" + +This ensures transparency about which submodule-architecture principle is being used and why. + +--- + +This document defines the submodule organisation, import conventions, and qualification rules for Julia code in the Control Toolbox ecosystem. The reference implementation is [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl); every package in the stack (including CTFlows) must follow the same pattern so that code, tests, and cross-package imports remain predictable and stable. + +## Core Principles + +1. **One submodule per concern** โ€” each submodule lives in its own subdirectory `src/<Name>/<Name>.jl` and has a single, well-defined responsibility. +2. **Manifest-only top-level** โ€” the package's top-level file only `include`s subdirectories and does `using .Submodule`; **it exports nothing**. +3. **Submodules export their public API** โ€” every submodule declares `export` for the symbols it considers public; internal helpers stay unexported and are reached via full qualification. +4. **Qualified imports for external packages** โ€” use `using PackageName: PackageName` or `import PackageName.SubModule` instead of bare `using`. +5. **Qualified usage everywhere** โ€” always call sibling-module or external symbols as `Submodule.function` / `Submodule.Type`; never rely on implicit scope. +6. **One-way dependency flow** โ€” submodules form a DAG; lower-level modules cannot import higher-level ones, and there are no cycles. + +## Submodule Directory Layout + +Each submodule occupies its own subdirectory. The `<Name>.jl` file at the root of that directory is the **manifest**; every other `.jl` file is `include`d by the manifest (possibly from nested subdirectories). + +Reference layout (CTSolvers): + +```text +src/ +โ”œโ”€โ”€ CTSolvers.jl # top-level manifest (exports nothing) +โ”œโ”€โ”€ Core/ +โ”‚ โ””โ”€โ”€ Core.jl # Core manifest +โ”œโ”€โ”€ Options/ +โ”‚ โ”œโ”€โ”€ Options.jl # manifest +โ”‚ โ”œโ”€โ”€ not_provided.jl +โ”‚ โ”œโ”€โ”€ option_value.jl +โ”‚ โ”œโ”€โ”€ option_definition.jl +โ”‚ โ””โ”€โ”€ extraction.jl +โ”œโ”€โ”€ Strategies/ +โ”‚ โ”œโ”€โ”€ Strategies.jl # manifest +โ”‚ โ”œโ”€โ”€ display_formatting.jl +โ”‚ โ”œโ”€โ”€ contract/ # nested subdirectory +โ”‚ โ”‚ โ”œโ”€โ”€ abstract_strategy.jl +โ”‚ โ”‚ โ”œโ”€โ”€ metadata.jl +โ”‚ โ”‚ โ””โ”€โ”€ strategy_options.jl +โ”‚ โ””โ”€โ”€ api/ +โ”‚ โ”œโ”€โ”€ registry.jl +โ”‚ โ”œโ”€โ”€ builders.jl +โ”‚ โ””โ”€โ”€ โ€ฆ +โ”œโ”€โ”€ Optimization/ +โ”œโ”€โ”€ Orchestration/ +โ”œโ”€โ”€ Modelers/ +โ”œโ”€โ”€ DOCP/ +โ””โ”€โ”€ Solvers/ +``` + +Rules: + +- A submodule directory contains exactly one manifest named after the module. +- Nested subdirectories (`contract/`, `api/`, โ€ฆ) are allowed for organisation. +- No logic in the manifest โ€” only imports, includes, and exports. + +## The Submodule Manifest Pattern + +Canonical structure of `src/<Name>/<Name>.jl`: + +```julia +""" +Module docstring โ€” purpose, responsibilities, dependencies. +""" +module Name + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +using SolverCore: SolverCore +using ADNLPModels: ADNLPModels + +# 2. Internal sibling-submodule imports +using ..Options +using ..Strategies +import ..Core as CTCore +using ..Core: AbstractTag + +# 3. Include files (ordered by internal dependency) +include(joinpath(@__DIR__, "abstract_types.jl")) +include(joinpath(@__DIR__, "contract.jl")) +include(joinpath(@__DIR__, "builders.jl")) + +# 4. Public API โ€” exports only +export AbstractX, ConcreteX +export build_x, validate_x + +end # module Name +``` + +Ordering rules: + +1. Docstring first (module-level documentation). +2. `module` declaration. +3. External-package imports. +4. Internal sibling imports. +5. `include(...)` calls (in dependency order). +6. `export` statements. +7. `end # module Name`. + +Section separators (`# ===โ€ฆ===`) are encouraged for readability. + +## External Package Import Style + +Three acceptable patterns, in order of preference: + +### 1. Name-qualified `using` (preferred for packages) + +```julia +using SolverCore: SolverCore +using ADNLPModels: ADNLPModels +``` + +This brings only the module name into scope. Call sites use `SolverCore.solve(...)`, `ADNLPModels.ADNLPModel(...)`. + +### 2. Submodule `import` + +```julia +import CTBase.Exceptions +``` + +This makes `Exceptions` available as a qualifier but does not bring its exported symbols into scope. Call sites use `Exceptions.NotImplemented(...)`, `Exceptions.IncorrectArgument(...)`. + +### 3. Symbol-qualified `import` (reserved for macros and heavily-used single symbols) + +```julia +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +``` + +Use only for: + +- Macros (cannot be qualified at usage site without awkwardness). +- Single symbols used pervasively where qualification would add noise (rare). + +### Forbidden patterns + +```julia +# โŒ Bare using โ€” pollutes namespace +using ADNLPModels + +# โŒ Symbol-list using โ€” opaque origin at call sites +using CTBase: AbstractModel, Dimension, validate, check +``` + +## Internal Submodule Import Style + +From within a submodule manifest, sibling submodules are reached via the parent scope `..`. + +### Importing a whole sibling + +```julia +using ..Options +using ..Strategies +``` + +Brings the submodule name into scope. Call sites use `Options.extract_option(...)`, `Strategies.metadata(...)`. + +### Aliasing + +```julia +import ..Core as CTCore +``` + +Use when the original name would conflict with another symbol, or when a shorter internal handle improves readability. + +### Specific symbol from a sibling + +```julia +using ..Core: AbstractTag +``` + +Use only when the symbol is pervasive within the current submodule *and* using it unqualified is unambiguous (typically for abstract types inherited from a Core module). + +## Qualification at Call Sites + +All references to external or sibling-submodule symbols must be qualified. This is the main consequence of the import conventions above. + +**โœ… Correct:** + +```julia +function Strategies.metadata(::Type{<:Modelers.ADNLP{P}}) where {P<:CPU} + return Strategies.StrategyMetadata( + Strategies.OptionDefinition( + name=:backend, type=Symbol, default=:optimized, + description="AD backend used by ADNLPModels", + ), + ) +end + +throw( + Exceptions.NotImplemented( + "Model building not implemented"; + required_method = "(modeler::$(typeof(modeler)))(prob::Optimization.AbstractOptimizationProblem, initial_guess)", + suggestion = "Implement the callable method for $(typeof(modeler)) to build NLP models", + context = "AbstractNLPModeler - required method implementation", + ), +) +``` + +**โŒ Wrong โ€” unqualified, fragile to refactoring:** + +```julia +# Where does StrategyMetadata come from? ADNLP? CPU? +function metadata(::Type{<:ADNLP{P}}) where {P<:CPU} + return StrategyMetadata( + OptionDefinition(name=:backend, type=Symbol, default=:optimized), + ) +end +``` + +### Why qualification matters + +- **Explicit origin at every call site** โ€” readers immediately see which submodule a symbol belongs to. +- **Refactor safety** โ€” if `Strategies` is renamed or moved, only the `using ..Strategies` line changes; all call sites stay correct as long as the new import uses the same name (or is aliased to it). +- **No accidental shadowing** โ€” qualified names cannot be captured by a local variable with the same stem. +- **Cross-package consistency** โ€” the same pattern works for external packages (`Exceptions.NotImplemented`) and sibling submodules (`Strategies.metadata`). + +## Dependency Order and the DAG + +The loading order in the top-level manifest must reflect a correct topological order of submodule dependencies. + +Reference DAG (CTSolvers): + +```text +Core + โ”œโ”€โ”€ Options + โ”‚ โ””โ”€โ”€ Strategies + โ”‚ โ”œโ”€โ”€ Orchestration + โ”‚ โ””โ”€โ”€ Optimization + โ”‚ โ”œโ”€โ”€ Modelers + โ”‚ โ”‚ โ””โ”€โ”€ DOCP + โ”‚ โ”‚ โ””โ”€โ”€ Solvers +``` + +Rules: + +- The top-level manifest lists `include`/`using` calls in topological order. +- A submodule can only `using ..Lower` where `Lower` was already loaded. +- **No cycles** โ€” if two submodules need each other, extract the shared concern into a lower-level submodule (typically `Core` or a dedicated `Types` module). + +## Exports and Public API + +Two-level rule: + +- **Submodule level** โ€” each submodule declares `export` at the end of its manifest for the symbols that form its public API. Internal helpers (names prefixed with `_`, or kept unexported by convention) stay unexported and are reached via full qualification. +- **Top-level (package) level** โ€” the package manifest **exports nothing**. It only loads submodules with `using .Submodule` so they become accessible as `Package.Submodule`. There are **no `export` statements** at the top level. + +### Consequences + +- Users access public symbols via `Package.Submodule.sym` โ€” explicit, stable, self-documenting. +- Adding a new public symbol in a submodule is a local change (one `export` line). +- Moving a symbol between submodules is visible at the call site (the qualification changes). +- Namespace conflicts between submodules cannot occur at package load time, because nothing is brought into the package-level scope. + +### Top-level manifest example + +```julia +""" + CTFlows + +Brief description of the package. + +# Architecture Overview + +CTFlows is organised into specialised submodules; all public symbols are +accessed via qualified paths (e.g. `CTFlows.Systems.AbstractSystem`). +""" +module CTFlows + +include(joinpath(@__DIR__, "Core", "Core.jl")) +using .Core + +include(joinpath(@__DIR__, "Systems", "Systems.jl")) +using .Systems + +include(joinpath(@__DIR__, "Modelers", "Modelers.jl")) +using .Modelers + +# โ€ฆ more submodules โ€ฆ + +# NO export statements here. + +end # module CTFlows +``` + +### User access patterns + +```julia +using CTFlows # brings no symbols into scope directly +CTFlows.Systems.AbstractSystem # fully qualified (recommended) + +using CTFlows.Systems # brings Systems exports into scope +AbstractSystem # unqualified (user's choice, at their own risk) +``` + +The `export` inside each submodule makes the unqualified form available to users who explicitly opt in via `using CTFlows.Submodule`. The package-level `using CTFlows` remains silent. + +## Proposed CTFlows Layout + +Informed by [`reports/design.md`](../../reports/design.md), the CTFlows submodule breakdown mirrors CTSolvers' separation of concerns: + +```text +src/ +โ”œโ”€โ”€ CTFlows.jl # top-level manifest, exports nothing +โ”œโ”€โ”€ Core/Core.jl # shared types and utilities +โ”œโ”€โ”€ Systems/Systems.jl # AbstractSystem + concrete systems + MultiPhaseSystem +โ”œโ”€โ”€ Flows/Flows.jl # AbstractFlow, Flow, MultiPhaseFlow +โ”œโ”€โ”€ Modelers/Modelers.jl # AbstractFlowModeler + concrete modelers +โ”œโ”€โ”€ Integrators/Integrators.jl # AbstractODEIntegrator + concrete integrators +โ”œโ”€โ”€ ADBackends/ADBackends.jl # AbstractADBackend + concrete backends +โ””โ”€โ”€ Pipelines/Pipelines.jl # build_system, build_flow, integrate, build_solution, solve +``` + +Dependency order (topologically sorted): + +```text +Core + โ”œโ”€โ”€ Systems + โ”œโ”€โ”€ Integrators + โ”œโ”€โ”€ ADBackends + โ”œโ”€โ”€ Modelers (depends on Systems, ADBackends) + โ”œโ”€โ”€ Flows (depends on Systems, Integrators) + โ””โ”€โ”€ Pipelines (depends on all of the above) +``` + +The `Options` and `Strategies` infrastructure is consumed from CTSolvers via standard package imports (`using CTSolvers: CTSolvers` then qualified calls like `CTSolvers.Strategies.AbstractStrategy`). + +## Quality Checklist + +Before finalising a submodule or a package restructure, verify: + +- [ ] Each submodule lives in its own subdirectory with a `<Name>.jl` manifest. +- [ ] The manifest contains only a docstring, `module`, imports, `include`s, `export`s, and `end`. +- [ ] External-package imports use `using Pkg: Pkg`, `import Pkg.Sub`, or (for macros) `import Pkg: sym`. +- [ ] Internal imports use `using ..Sibling`, `import ..Sibling as Alias`, or `using ..Sibling: Sym`. +- [ ] All references to sibling or external symbols are fully qualified at call sites. +- [ ] The dependency graph is acyclic and respected by the top-level loading order. +- [ ] Each submodule declares `export` for its public API. +- [ ] The top-level package manifest contains **no** `export` statements. diff --git a/windsurf-skill-based/rules/testing-execution.md b/windsurf-skill-based/rules/testing-execution.md new file mode 100644 index 00000000..64f5bdc0 --- /dev/null +++ b/windsurf-skill-based/rules/testing-execution.md @@ -0,0 +1,104 @@ +--- +trigger: model_decision +--- + +# Julia Test Execution Guide + +## ๐Ÿค– **Agent Directive** + +**When applying this rule, explicitly state**: "๐Ÿงช **Applying Testing Rule**: [specific testing principle being applied]" + +This ensures transparency about which testing standard is being used and why. + +--- + +This document defines how to run tests for the CTFlows.jl project. For test creation standards, see `testing.md`. + +## Running Tests + +For detailed instructions on how to run tests (specific tests, test groups, or all tests), please refer to the testing guide: + +**See**: `test/README.md` + +This file contains comprehensive information about: + +- Running all enabled tests +- Running specific test groups using glob patterns +- Running individual test files +- Running all tests including optional/long tests +- Generating coverage reports + +### Capturing Test Output (Agents) + +When running tests from the terminal (especially AI agents), **always pipe the output to a file via `tee`** instead of truncating with `tail -N`. Truncated output frequently hides the first failure or the compilation errors that trigger subsequent issues, forcing a second run. + +**โœ… Good โ€” capture full log, inspect tail afterwards:** + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/systems/test_abstract_system"])' \ + 2>&1 | tee /tmp/ctflows_test.log +# then, if needed, grep/tail the saved log without rerunning: +grep -E "Error|Fail|Test Summary" /tmp/ctflows_test.log +tail -200 /tmp/ctflows_test.log +``` + +**โŒ Avoid โ€” truncated stream lost if you need more context:** + +```bash +julia --project -e '...' 2>&1 | tail -120 # if the relevant error is above line -120, you must rerun +``` + +**Rules of thumb:** + +- Save to `/tmp/<pkg>_<scope>.log` (e.g., `/tmp/ctflows_test_systems.log`) so multiple concurrent sessions do not collide. +- Prefer `tee` on the full run; then use `grep`, `rg`, `tail`, or `less` on the saved file. +- Clean up `/tmp/*.log` files periodically; do not commit them. + +## Quick Test Commands + +### Convenience Alias: `jtest` + +If the `jtest` alias is defined in your shell (typically in `~/.zsh_aliases`), you can use it as a shortcut: + +```bash +# Alias definition (in ~/.zsh_aliases) +alias jtest="/Users/ocots/bin/julia_test.sh" +``` + +The `jtest` script wraps `Pkg.test()`: + +```bash +# Run all tests +jtest + +# Run specific test suite +jtest suite/systems/test_abstract_system +``` + +**Script location**: `~/bin/julia_test.sh` (user-local). If you have this alias, it provides a convenient way to run tests without typing the full Julia invocation each time. + +### Run all tests + +```bash +julia --project=@. -e 'using Pkg; Pkg.test()' +``` + +### Run specific test group + +```bash +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["ocp"])' +``` + +### Generate coverage report + +```bash +julia --project=@. -e 'using Pkg; Pkg.test("CTFlows"; coverage=true); include("test/coverage.jl")' +``` + +## References + +- Test README: `test/README.md` +- Test creation standards: `testing.md` +- Test workflows: `@/test-julia`, `@/test-julia-debug` +- Shared test problems: `test/problems/TestProblems.jl` +- Test runner: Uses `CTBase.TestRunner` extension diff --git a/windsurf-skill-based/skills/architecture/SKILL.md b/windsurf-skill-based/skills/architecture/SKILL.md new file mode 100644 index 00000000..35531b79 --- /dev/null +++ b/windsurf-skill-based/skills/architecture/SKILL.md @@ -0,0 +1,39 @@ +--- +name: architecture +description: SOLID principles and design patterns for Julia code: SRP, OCP, LSP, ISP, DIP, DRY, KISS, YAGNI, multiple dispatch, type hierarchies, composition over inheritance, layered architecture, anti-patterns (God Object, Primitive Obsession, Feature Envy). Invoke when introducing new types, new dependencies, restructuring modules, or reviewing code design. +--- + +# Julia Architecture and Design Principles + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "๐Ÿ“‹ **Applying Architecture Rule**: [specific principle being applied]" + +--- + +## Core Principles (summary) + +1. **SRP** โ€” Each module, function, and type has one clear purpose +2. **OCP** โ€” Open for extension, closed for modification +3. **LSP** โ€” Subtypes must honor parent contracts +4. **ISP** โ€” Keep interfaces small and focused +5. **DIP** โ€” Depend on abstractions, not concrete implementations +6. **DRY** โ€” No code duplication +7. **KISS** โ€” Prefer simple solutions +8. **YAGNI** โ€” Don't add functionality until actually needed + +## Supporting files โ€” read as needed + +- `solid.md` โ€” Full SOLID principles (SRP, OCP, LSP, ISP, DIP) with Julia examples +- `other-principles.md` โ€” DRY, KISS, YAGNI +- `julia-patterns.md` โ€” Multiple dispatch, type hierarchies, composition over inheritance, parametric types +- `module-organization.md` โ€” Layered architecture, separation of concerns +- `anti-patterns.md` โ€” God Object, Primitive Obsession, Feature Envy + Quality Checklist + References + +Read only the file(s) relevant to the current task. When in doubt, read `solid.md` first. + +## Related skills + +- `docstrings` โ€” Documentation standards +- `testing-creation` โ€” Testing standards +- `type-stability` โ€” Type stability standards diff --git a/windsurf-skill-based/skills/architecture/anti-patterns.md b/windsurf-skill-based/skills/architecture/anti-patterns.md new file mode 100644 index 00000000..1cbd9b2a --- /dev/null +++ b/windsurf-skill-based/skills/architecture/anti-patterns.md @@ -0,0 +1,109 @@ +# Common Anti-Patterns and Quality Checklist + +## Quality Checklist + +Before finalizing code, verify: + +- [ ] Each function has a single, clear responsibility +- [ ] Abstract types define clear interfaces +- [ ] Subtypes honor parent contracts (LSP) +- [ ] No hard-coded type checks (`isa`, `typeof`) +- [ ] Dependencies are on abstractions, not concrete types +- [ ] No code duplication (DRY) +- [ ] Solution is as simple as possible (KISS) +- [ ] No premature features (YAGNI) +- [ ] Multiple dispatch used appropriately +- [ ] Type hierarchies reflect conceptual relationships +- [ ] Module organization follows layered architecture + +--- + +## God Object + +**โŒ Avoid:** One object that does everything + +```julia +struct System + data::Dict + config::Dict + state::Dict + # 50+ fields +end + +# 100+ methods operating on System +``` + +**โœ… Instead:** Split into focused components + +```julia +struct DataManager + data::Dict +end + +struct ConfigManager + config::Dict +end + +struct StateManager + state::Dict +end +``` + +--- + +## Primitive Obsession + +**โŒ Avoid:** Using primitives instead of domain types + +```julia +function create_problem(n::Int, m::Int, t0::Float64, tf::Float64) + # What do these numbers mean? +end +``` + +**โœ… Instead:** Use domain types + +```julia +struct Dimensions + state::Int + control::Int +end + +struct TimeInterval + initial::Float64 + final::Float64 +end + +function create_problem(dims::Dimensions, time::TimeInterval) + # Clear meaning +end +``` + +--- + +## Feature Envy + +**โŒ Avoid:** Methods that use more of another type's data + +```julia +function compute_cost(model::Model, data::Data) + # Uses mostly data fields, not model fields + return data.a * data.b + data.c +end +``` + +**โœ… Instead:** Move method to appropriate type + +```julia +function compute_cost(data::Data) + return data.a * data.b + data.c +end +``` + +--- + +## References + +- [Julia Style Guide](https://docs.julialang.org/en/v1/manual/style-guide/) +- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) +- [Design Patterns in Julia](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md) diff --git a/windsurf-skill-based/skills/architecture/julia-patterns.md b/windsurf-skill-based/skills/architecture/julia-patterns.md new file mode 100644 index 00000000..8f52f6c6 --- /dev/null +++ b/windsurf-skill-based/skills/architecture/julia-patterns.md @@ -0,0 +1,73 @@ +# Julia-Specific Patterns + +## Multiple Dispatch + +Use multiple dispatch for extensibility and clarity: + +```julia +# Define behavior for different type combinations +function combine(a::Number, b::Number) + return a + b +end + +function combine(a::Vector, b::Vector) + return vcat(a, b) +end + +function combine(a::String, b::String) + return a * b +end + +# Extensible: add new methods without modifying existing code +``` + +--- + +## Type Hierarchies + +Design type hierarchies that reflect conceptual relationships: + +```julia +# Clear hierarchy +abstract type AbstractStrategy end +abstract type AbstractDirectMethod <: AbstractStrategy end +abstract type AbstractIndirectMethod <: AbstractStrategy end + +struct DirectShooting <: AbstractDirectMethod end +struct DirectCollocation <: AbstractDirectMethod end +struct IndirectShooting <: AbstractIndirectMethod end +``` + +--- + +## Composition Over Inheritance + +Prefer composition (has-a) over inheritance (is-a) when appropriate: + +```julia +# Composition: Model has a solver +struct OptimizationModel + problem::AbstractProblem + solver::AbstractSolver + options::NamedTuple +end + +# Not: OptimizationModel <: AbstractSolver +``` + +--- + +## Parametric Types + +Use parametric types for type stability and flexibility: + +```julia +# Type-stable with parameters +struct Container{T} + items::Vector{T} +end + +# Flexible: works with any type +c1 = Container([1, 2, 3]) # Container{Int} +c2 = Container([1.0, 2.0, 3.0]) # Container{Float64} +``` diff --git a/windsurf-skill-based/skills/architecture/module-organization.md b/windsurf-skill-based/skills/architecture/module-organization.md new file mode 100644 index 00000000..dc60fbc0 --- /dev/null +++ b/windsurf-skill-based/skills/architecture/module-organization.md @@ -0,0 +1,69 @@ +# Module Organization + +## Layered Architecture + +Organize code in layers with clear dependencies: + +```text +Low-level (Core types, utilities) + โ†“ +Mid-level (Business logic, algorithms) + โ†“ +High-level (User-facing API, orchestration) +``` + +**Example:** + +```julia +# Low-level: Core types +module Types + abstract type AbstractProblem end + struct Problem <: AbstractProblem + # ... + end +end + +# Mid-level: Algorithms +module Solvers + using ..Types + function solve(p::AbstractProblem) + # ... + end +end + +# High-level: User API +module API + using ..Types + using ..Solvers + export solve, Problem +end +``` + +--- + +## Separation of Concerns + +Keep different concerns in separate modules: + +```julia +# Validation logic +module Validation + function validate_dimensions(n, m) + # ... + end +end + +# Parsing logic +module Parsing + function parse_input(text) + # ... + end +end + +# Business logic +module Core + using ..Validation + using ..Parsing + # ... +end +``` diff --git a/windsurf-skill-based/skills/architecture/other-principles.md b/windsurf-skill-based/skills/architecture/other-principles.md new file mode 100644 index 00000000..cc68194e --- /dev/null +++ b/windsurf-skill-based/skills/architecture/other-principles.md @@ -0,0 +1,93 @@ +# Other Design Principles + +## DRY - Don't Repeat Yourself + +Avoid code duplication. Every piece of knowledge should have a single representation. + +**โœ… Good - Extract common logic:** + +```julia +function validate_positive(x, name) + x > 0 || throw(IncorrectArgument("$name must be positive")) +end + +function create_model(n::Int, m::Int) + validate_positive(n, "n") + validate_positive(m, "m") + return Model(n, m) +end +``` + +**โŒ Bad - Duplicated validation:** + +```julia +function create_model(n::Int, m::Int) + n > 0 || throw(ArgumentError("n must be positive")) + m > 0 || throw(ArgumentError("m must be positive")) + return Model(n, m) +end + +function create_problem(n::Int, m::Int) + n > 0 || throw(ArgumentError("n must be positive")) # Duplicated! + m > 0 || throw(ArgumentError("m must be positive")) # Duplicated! + return Problem(n, m) +end +``` + +--- + +## KISS - Keep It Simple, Stupid + +Prefer simple solutions over complex ones. Avoid over-engineering. + +**โœ… Good - Simple and clear:** + +```julia +function compute_mean(xs) + return sum(xs) / length(xs) +end +``` + +**โŒ Bad - Over-engineered:** + +```julia +function compute_mean(xs) + accumulator = zero(eltype(xs)) + counter = 0 + for x in xs + accumulator = accumulator + x + counter = counter + 1 + end + return accumulator / counter +end +``` + +--- + +## YAGNI - You Aren't Gonna Need It + +Don't add functionality until it's actually needed. + +**โœ… Good - Implement what's needed:** + +```julia +struct Model + coeffs::Vector{Float64} +end + +function evaluate(m::Model, x) + return dot(m.coeffs, x) +end +``` + +**โŒ Bad - Premature features:** + +```julia +struct Model + coeffs::Vector{Float64} + cache::Dict{Vector, Float64} # Not needed yet + optimization_history::Vector # Not needed yet + metadata::Dict{Symbol, Any} # Not needed yet + version::String # Not needed yet +end +``` diff --git a/windsurf-skill-based/skills/architecture/solid.md b/windsurf-skill-based/skills/architecture/solid.md new file mode 100644 index 00000000..5e497c7c --- /dev/null +++ b/windsurf-skill-based/skills/architecture/solid.md @@ -0,0 +1,284 @@ +# SOLID Principles in Julia + +## Single Responsibility Principle (SRP) + +Every module, function, and type should have a single, well-defined responsibility. + +**โœ… Good - Focused responsibilities:** + +```julia +# Parsing responsibility +function parse_ocp_input(text::String) + return parsed_data +end + +# Validation responsibility +function validate_ocp_data(data) + return is_valid, errors +end + +# Processing responsibility +function solve_ocp(data) + return solution +end +``` + +**โŒ Bad - Too many responsibilities:** + +```julia +function handle_ocp(text::String) + parsed = parse(text) # Parsing + validate(parsed) # Validation + solution = solve(parsed) # Processing + save_to_file(solution, "out") # I/O + return format_output(solution) # Formatting +end +``` + +**Red flags:** + +- Function names with "and" or "or" +- Functions longer than 50 lines +- Multiple `if-else` branches handling different concerns +- Modules mixing unrelated functionality + +--- + +## Open/Closed Principle (OCP) + +Software should be open for extension but closed for modification. + +**โœ… Good - Extensible via abstract types:** + +```julia +# Define abstract interface +abstract type AbstractOptimizationProblem end + +# Existing implementation +struct LinearProblem <: AbstractOptimizationProblem + A::Matrix + b::Vector +end + +# Solver works with any AbstractOptimizationProblem +function solve(problem::AbstractOptimizationProblem) + # Generic solving logic +end + +# NEW: Extend without modifying existing code +struct NonlinearProblem <: AbstractOptimizationProblem + f::Function + x0::Vector +end +# Solver automatically works via multiple dispatch +``` + +**โŒ Bad - Hard-coded type checks:** + +```julia +function solve(problem) + if problem isa LinearProblem + # Linear solving + elseif problem isa NonlinearProblem + # Nonlinear solving + # Need to modify for every new type! + end +end +``` + +**How to apply:** + +- Use abstract types to define interfaces +- Leverage multiple dispatch for extensibility +- Avoid type checking with `isa` or `typeof` +- Design type hierarchies that allow new subtypes + +--- + +## Liskov Substitution Principle (LSP) + +Subtypes must be substitutable for their parent types without breaking functionality. + +**โœ… Good - Consistent interface:** + +```julia +abstract type AbstractModel end + +# Contract: all models must implement `evaluate` +function evaluate(model::AbstractModel, x) + throw(NotImplemented("evaluate not implemented for $(typeof(model))")) +end + +# Subtype honors contract +struct LinearModel <: AbstractModel + coeffs::Vector +end + +function evaluate(model::LinearModel, x) + return dot(model.coeffs, x) # Returns a number +end + +# Generic code works with any AbstractModel +function optimize(model::AbstractModel, x0) + value = evaluate(model, x0) # Safe for any model + # ... +end +``` + +**โŒ Bad - Subtype breaks contract:** + +```julia +struct BrokenModel <: AbstractModel + data::String +end + +function evaluate(model::BrokenModel, x) + return "error: invalid" # Returns String, not number! +end + +# This breaks unexpectedly +function optimize(model::AbstractModel, x0) + value = evaluate(model, x0) + gradient = value * 2 # ERROR if value is String! +end +``` + +**How to apply:** + +- Define clear contracts for abstract types (via docstrings) +- Ensure all subtypes implement required methods consistently +- Return types should be compatible across hierarchy +- Test that generic code works with all subtypes + +**Testing LSP:** + +```julia +@testset "Liskov Substitution" begin + # Test that all subtypes work with generic code + for ModelType in [LinearModel, QuadraticModel, CustomModel] + model = ModelType(test_params...) + @test evaluate(model, x) isa Number + @test optimize(model, x0) isa Solution + end +end +``` + +--- + +## Interface Segregation Principle (ISP) + +Keep interfaces small and focused. Don't force clients to depend on methods they don't use. + +**โœ… Good - Small, focused interfaces:** + +```julia +# Separate capabilities +abstract type Evaluable end +abstract type Differentiable end + +# Types implement only what they need +struct SimpleFunction <: Evaluable + f::Function +end + +struct SmoothFunction <: Union{Evaluable, Differentiable} + f::Function + df::Function +end + +# Clients depend only on what they need +function plot_function(f::Evaluable, xs) + return [evaluate(f, x) for x in xs] +end + +function optimize(f::Differentiable, x0) + return gradient_descent(f, x0) +end +``` + +**โŒ Bad - Bloated interface:** + +```julia +# Forces all types to implement everything +abstract type MathFunction end + +# Required methods (even if not needed): +evaluate(f::MathFunction, x) = error("not implemented") +gradient(f::MathFunction, x) = error("not implemented") +hessian(f::MathFunction, x) = error("not implemented") +integrate(f::MathFunction, a, b) = error("not implemented") + +# Simple function forced to implement everything +struct SimpleFunction <: MathFunction + f::Function +end + +evaluate(sf::SimpleFunction, x) = sf.f(x) +gradient(sf::SimpleFunction, x) = error("not differentiable") # Forced! +hessian(sf::SimpleFunction, x) = error("not differentiable") # Forced! +integrate(sf::SimpleFunction, a, b) = error("not integrable") # Forced! +``` + +**How to apply:** + +- Create small, focused abstract types +- Use `Union` types for multiple interfaces +- Don't force implementations of unused methods +- Export only necessary functions + +--- + +## Dependency Inversion Principle (DIP) + +Depend on abstractions, not concrete implementations. + +**โœ… Good - Depend on abstractions:** + +```julia +# High-level abstraction +abstract type DataStore end + +# High-level module depends on abstraction +struct DataProcessor + store::DataStore # Abstract type +end + +function process(dp::DataProcessor, data) + save(dp.store, data) # Works with any DataStore +end + +# Low-level implementations +struct FileStore <: DataStore + path::String +end + +struct DatabaseStore <: DataStore + connection::DBConnection +end + +# Easy to swap implementations +processor1 = DataProcessor(FileStore("data.txt")) +processor2 = DataProcessor(DatabaseStore(conn)) +``` + +**โŒ Bad - Depend on concrete types:** + +```julia +# Tightly coupled to file system +struct DataProcessor + file_path::String +end + +function process(dp::DataProcessor, data) + write(dp.file_path, data) # Hard-coded to files +end + +# Can't switch to database without modifying DataProcessor +``` + +**How to apply:** + +- Define abstract types for dependencies +- Pass abstract types as arguments +- Use dependency injection +- Avoid hard-coding concrete types diff --git a/windsurf-skill-based/skills/docstrings/SKILL.md b/windsurf-skill-based/skills/docstrings/SKILL.md new file mode 100644 index 00000000..eecb472a --- /dev/null +++ b/windsurf-skill-based/skills/docstrings/SKILL.md @@ -0,0 +1,194 @@ +--- +name: docstrings +description: Julia docstring standards for CTFlows/Control Toolbox: TYPEDEF and TYPEDSIGNATURES macros from DocStringExtensions, required sections (Arguments, Fields, Returns, Throws, Example, Notes, References, See also), internal @ref vs external @extref cross-reference syntax, safe runnable examples policy, function/struct/abstract type templates. Invoke when writing or reviewing docstrings. +--- + +# Julia Documentation Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "๐Ÿ“š **Applying Documentation Rule**: [specific documentation principle being applied]" + +This ensures transparency about which documentation standard is being used and why. + +--- + +This document defines the documentation standards for the Control Toolbox project. All Julia code (functions, structs, macros, modules) must be documented following these guidelines. + +> ๐Ÿ“Œ **Project-specific names** (package name, submodule qualification style, `@extref` packages): read `project.md`. Replace its content when adapting this skill to another package. + +## Core Principles + +1. **Completeness**: Every exported symbol and significant internal component must have a docstring +2. **Accuracy**: Documentation must reflect actual behavior, not aspirational or outdated information +3. **Clarity**: Write for users who understand Julia but may be unfamiliar with the specific domain +4. **Consistency**: Follow the templates and conventions defined here + +## Docstring Placement + +- Docstrings go **immediately above** the declaration they document +- No blank lines between docstring and declaration +- For multi-method functions, document the most general signature or provide method-specific docstrings + +## Required Docstring Structure + +Every docstring should contain: + +1. **Signature line** (for functions): Use `$(TYPEDSIGNATURES)` from DocStringExtensions +2. **One-sentence summary**: Clear, concise description of purpose +3. **Detailed description** (if needed): Explain behavior, constraints, invariants, edge cases +4. **Structured sections** (as applicable): + - `# Arguments`: For functions/macros + - `# Fields`: For structs/types + - `# Returns`: For functions that return values + - `# Throws`: For functions that may throw exceptions + - `# Example` or `# Examples`: Demonstrate usage + - `# Notes`: Performance considerations, stability warnings, implementation details + - `# References`: Citations to papers, algorithms, or external documentation + - `See also:`: Related functions/types with `[@ref]` links + +## Cross-References + +### Internal References + +For symbols within the current package or its dependencies, use `[@ref]` syntax with **full module path** including the root package and submodules: + +```julia +See also: [`PackageName.Submodule.related_function`](@ref), [`PackageName.Submodule.RelatedType`](@ref) +``` + +**Rules for @ref:** + +1. Use full module path including root package (e.g., `CTFlows.Integrators.SciMLTag`, not just `SciMLTag`) +2. Include all nested submodules in the path +3. Only use for symbols documented in the current package's documentation + +**Examples:** + +โœ… **Correct internal references:** + +- [`CTFlows.Integrators.SciMLTag`](@ref) +- [`CTFlows.Options.OptionValue`](@ref) +- [`CTFlows.Systems.AbstractSystem`](@ref) + +โŒ **Incorrect internal references:** + +- [`SciMLTag`](@ref) # Missing module qualification +- [`Integrators.SciMLTag`](@ref) # Missing root package name + +### External Package References + +For symbols in external packages that are not part of the current documentation build, use `[@extref]` syntax with the **full module path** including submodules: + +```julia +See also: [`CTSolvers.Options.OptionValue`](@extref) +``` + +**Rules for @extref:** + +1. Use the complete module path (e.g., `CTSolvers.Options.OptionValue`, not just `OptionValue`) +2. Include all submodules in the path +3. Only use for symbols that are not documented in the current package's documentation +4. Use when the symbol is from a dependency that has its own separate documentation + +**Examples:** + +โœ… **Correct external references:** + +- [`CTSolvers.Options.OptionValue`](@extref) +- [`CTBase.Exceptions.IncorrectArgument`](@extref) +- [`CTModels.Init.build_initial_guess`](@extref) + +โŒ **Incorrect external references:** + +- [`OptionValue`](@extref) # Missing module path +- [`CTSolvers.OptionValue`](@ref) # Wrong syntax for external symbol + +**When to use which:** + +- Use `[@ref]` for symbols within OptimalControl or its included documentation +- Use `[@extref]` for symbols from external packages with separate documentation + +## Templates (โ†’ read `templates.md`) + +Copy-paste templates for functions, structs, and abstract types are in `templates.md`. + +- **Function template** โ€” `$(TYPEDSIGNATURES)`, Arguments, Returns, Throws, Example, Notes, See also +- **Struct template** โ€” `$(TYPEDEF)`, Fields, Constructor Validation, Example, Notes, See also +- **Abstract type template** โ€” `$(TYPEDEF)`, Interface Requirements, Example, See also + +## Example Safety Policy + +Examples in docstrings must be **safe and reproducible**: + +### โœ… Safe Examples + +- Pure computations with deterministic results +- Constructors with simple, valid inputs +- Queries on created objects +- Examples that start with `using CTModels.ModuleName` + +### โŒ Unsafe Examples + +- File system operations (reading/writing files) +- Network requests +- Database operations +- Git operations +- Non-deterministic behavior (random numbers without seed, timing-dependent code) +- Long-running computations (>1 second) +- Dependencies on external state or global variables + +### Fallback for Complex Cases + +If a safe, runnable example cannot be provided: +- Use a plain code block (\`\`\`julia) instead of REPL block (\`\`\`julia-repl) +- Show usage patterns without claiming specific output +- Provide a conceptual sketch of how to use the API + +Example: +```julia +# Example +\`\`\`julia +# Conceptual usage pattern +ocp = Model(...) +constraint!(ocp, :state, 0.0, :initial) +sol = solve(ocp, strategy=MyStrategy()) +\`\`\` +``` + +## Module Prefix Convention + +- **Exported symbols**: Use directly without module prefix + ```julia-repl + julia> using CTModels.Options + julia> opt = OptionValue(100, :user) # OptionValue is exported + ``` + +- **Internal symbols**: Use module prefix + ```julia-repl + julia> using CTModels.Options + julia> Options.internal_function(...) # Not exported + ``` + +## DocStringExtensions Macros + +This project uses [DocStringExtensions.jl](https://github.com/JuliaDocs/DocStringExtensions.jl): + +- `$(TYPEDEF)`: Auto-generates type signature for structs/abstract types +- `$(TYPEDSIGNATURES)`: Auto-generates function signature with types +- Use these instead of manually writing signatures + +## Quality Checklist + +Before finalizing a docstring, verify: + +- [ ] Docstring is directly above the declaration (no blank lines) +- [ ] Uses `$(TYPEDEF)` or `$(TYPEDSIGNATURES)` where applicable +- [ ] One-sentence summary is clear and accurate +- [ ] All arguments/fields are documented with types and descriptions +- [ ] Return value is documented (if applicable) +- [ ] Exceptions are documented (if thrown) +- [ ] Example is safe, runnable, and demonstrates typical usage +- [ ] Cross-references use `[@ref]` syntax for related items +- [ ] No invented behavior or aspirational features +- [ ] Consistent with project style and terminology diff --git a/windsurf-skill-based/skills/docstrings/project.md b/windsurf-skill-based/skills/docstrings/project.md new file mode 100644 index 00000000..0d54ad73 --- /dev/null +++ b/windsurf-skill-based/skills/docstrings/project.md @@ -0,0 +1,65 @@ +# Project: CTFlows + +> **Adaptation guide** โ€” This file contains everything that changes between packages in the control-toolbox ecosystem. +> When copying this skill to another package, only edit this file (and the `description` field in `SKILL.md`). + +--- + +## Package + +- **Name**: `CTFlows` +- **Root module**: `CTFlows` + +## Submodule Qualification + +Public symbols are always accessed through their submodule, never through the root package: + +```julia +CTFlows.Flows.build_flow(...) # โœ… correct +CTFlows.build_flow(...) # โŒ wrong โ€” nothing exported at root level +``` + +## Exposed Submodules + +| Submodule | Exported symbols (examples) | +| --- | --- | +| `CTFlows.Common` | `AbstractTag`, `AbstractTrait`, `Autonomous`, `Fixed` | +| `CTFlows.Data` | `VectorField`, `HamiltonianVectorField` | +| `CTFlows.Systems` | `AbstractSystem`, `AbstractStateSystem` | +| `CTFlows.Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow` | +| `CTFlows.Integrators` | `AbstractIntegrator`, `AbstractIntegrationResult` | +| `CTFlows.Solutions` | `AbstractSolution` | + +## Cross-Reference Style + +```julia +# In docstrings, use the full qualified path: +# See also: [`CTFlows.Flows.build_flow`](@ref) +# See also: [`CTFlows.Flows.AbstractFlow`](@ref) +``` + +For inter-package cross-references (e.g., to CTBase): + +```julia +# See also: [`CTBase.Exceptions.NotImplemented`](@extref) +``` + +## `@extref` Packages Registered in `docs/make.jl` + +- `CTBase` โ€” base types, exceptions, docstring extensions +- `CTSolvers` โ€” strategy metadata, options + +## Module Prefix in Docstrings + +When a docstring is defined inside `src/Flows/flow.jl`, the public prefix shown in the docs is `CTFlows.Flows.`: + +```julia +""" +$(TYPEDSIGNATURES) + +Build a flow from a system and an integrator. + +See also: [`CTFlows.Flows.AbstractFlow`](@ref), [`CTFlows.Systems.AbstractSystem`](@ref) +""" +function build_flow(sys::AbstractSystem, integrator::AbstractIntegrator) +``` diff --git a/windsurf-skill-based/skills/docstrings/templates.md b/windsurf-skill-based/skills/docstrings/templates.md new file mode 100644 index 00000000..ec742201 --- /dev/null +++ b/windsurf-skill-based/skills/docstrings/templates.md @@ -0,0 +1,123 @@ +# Docstring Templates + +## Function Template + +```julia +""" +$(TYPEDSIGNATURES) + +One-sentence description of what the function does. + +Optional detailed explanation covering: +- Behavior and semantics +- Constraints and preconditions +- Common use cases or patterns + +# Arguments +- `arg1::Type1`: Description of first argument +- `arg2::Type2`: Description of second argument + +# Returns +- `ReturnType`: Description of return value + +# Throws +- `ExceptionType`: When and why this exception is thrown + +# Example +\`\`\`julia-repl +julia> using CTModels.ModuleName + +julia> result = function_name(arg1, arg2) +expected_output +\`\`\` + +# Notes +- Performance characteristics (if relevant) +- Thread safety (if relevant) +- Stability guarantees + +See also: [`PackageName.ModuleName.related_function`](@ref), [`PackageName.ModuleName.RelatedType`](@ref) +""" +function function_name(arg1::Type1, arg2::Type2)::ReturnType + # implementation +end +``` + +--- + +## Struct Template + +```julia +""" +$(TYPEDEF) + +One-sentence description of what this type represents. + +Optional detailed explanation covering: +- Purpose and design intent +- Invariants that must be maintained +- Relationship to other types + +# Fields +- `field1::Type1`: Description and constraints +- `field2::Type2`: Description and constraints + +# Constructor Validation + +Describe any validation performed by constructors (if applicable). + +# Example +\`\`\`julia-repl +julia> using CTModels.ModuleName + +julia> obj = StructName(value1, value2) +StructName(...) + +julia> obj.field1 +value1 +\`\`\` + +# Notes +- Mutability status (if not obvious from declaration) +- Performance considerations + +See also: [`ModuleName.related_type`](@ref), [`ModuleName.constructor_function`](@ref) +""" +struct StructName{T} + field1::Type1 + field2::Type2 +end +``` + +--- + +## Abstract Type Template + +```julia +""" +$(TYPEDEF) + +One-sentence description of the abstraction. + +Detailed explanation of: +- What types should subtype this +- Contract/interface requirements for subtypes +- Common behavior across all subtypes + +# Interface Requirements + +List methods that subtypes must implement: +- `required_method(::SubType)`: Description + +# Example +\`\`\`julia-repl +julia> using CTModels.ModuleName + +julia> MyType <: AbstractTypeName +true +\`\`\` + +See also: [`ModuleName.ConcreteSubtype1`](@ref), [`ModuleName.ConcreteSubtype2`](@ref) +""" +abstract type AbstractTypeName end +``` diff --git a/windsurf-skill-based/skills/exceptions/SKILL.md b/windsurf-skill-based/skills/exceptions/SKILL.md new file mode 100644 index 00000000..9aa76404 --- /dev/null +++ b/windsurf-skill-based/skills/exceptions/SKILL.md @@ -0,0 +1,171 @@ +--- +name: exceptions +description: Julia exception standards for CTFlows/Control Toolbox: seven exception types (IncorrectArgument, PreconditionError, NotImplemented, ParsingError, AmbiguousDescription, ExtensionError, SolverFailure) with fields, usage patterns, anti-patterns, and testing. Invoke when adding error paths, contract stubs, argument validation, state machine violations, extension stubs, or solver failures. +--- + +# Julia Exception Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "โš ๏ธ **Applying Exception Rule**: [specific exception principle being applied]" + +This ensures transparency about which exception standard is being used and why. + +--- + +This document defines the exception handling standards for the Control Toolbox project. All error conditions must be handled using structured, informative exceptions that provide clear guidance to users. + +## Core Principles + +1. **Clear Messages**: Error messages must be immediately understandable +2. **Actionable Suggestions**: Provide guidance on how to fix the problem +3. **Rich Context**: Include what was expected, what was received, and where +4. **User-Friendly**: Format errors for end users, not just developers + +## Exception Types (โ†’ read `exception-types.md`) + +Seven types are available. Read `exception-types.md` for fields, examples, "when to use", and the Quick Reference table. + +- **`IncorrectArgument`** โ€” invalid argument value, type mismatch, out of range +- **`PreconditionError`** โ€” wrong call order, state machine violation, uninitialized object +- **`NotImplemented`** โ€” abstract interface stub, extension point, unimplemented feature +- **`ParsingError`** โ€” DSL syntax error, configuration parsing +- **`AmbiguousDescription`** โ€” symbol tuple not matched in description-based API +- **`ExtensionError`** โ€” optional dependency (weak dep) not loaded +- **`SolverFailure`** โ€” ODE integrator or optimization solver failure + +## Best Practices + +### Write Clear Messages + +**โœ… Good - Specific and clear:** + +```julia +throw(IncorrectArgument( + "State dimension must be positive", + got="n = -1", + expected="n > 0", + suggestion="Provide a positive integer for state dimension" +)) +``` + +**โŒ Bad - Vague:** + +```julia +throw(IncorrectArgument("Invalid input")) +``` + +### Use Appropriate Exception Types + +**โœ… Good - Correct type:** + +```julia +throw(IncorrectArgument("n must be positive", got="n = -1", expected="n > 0")) +throw(PreconditionError("Cannot modify frozen OCP", reason="OCP is immutable")) +throw(NotImplemented("solve! not implemented", required_method="solve!(::MyStrategy, ...)")) +``` + +**โŒ Bad - Wrong type:** + +```julia +throw(IncorrectArgument("OCP already finalized")) # Should be PreconditionError +throw(PreconditionError("n must be positive")) # Should be IncorrectArgument +``` + +## Common Patterns + +### Validation Pattern + +```julia +function validate_dimension(n::Int, name::String) + if n <= 0 + throw(IncorrectArgument( + "Dimension must be positive", + got="$name = $n", + expected="$name > 0", + suggestion="Provide a positive integer for $name" + )) + end +end +``` + +### State Machine Pattern + +```julia +mutable struct OCP + state_defined::Bool +end + +function state!(ocp::OCP, n::Int) + if ocp.state_defined + throw(PreconditionError( + "Cannot call state! twice", + reason="state has already been defined for this OCP", + suggestion="Create a new OCP instance" + )) + end + ocp.state_defined = true +end +``` + +### Interface Pattern + +```julia +abstract type AbstractStrategy end + +function solve!(strategy::AbstractStrategy, problem) + throw(NotImplemented( + "solve! must be implemented for each strategy type", + type_info=string(typeof(strategy)), + suggestion="Define solve!(::$(typeof(strategy)), problem) or import the relevant package" + )) +end +``` + +## Testing Exceptions + +```julia +@testset "Exception Types" begin + @test_throws IncorrectArgument invalid_function(bad_arg) + + err = try + invalid_function(bad_arg) + catch e + e + end + @test err isa IncorrectArgument + @test occursin("Invalid criterion", err.msg) +end +``` + +## Quality Checklist + +Before finalizing exception handling, verify: + +- [ ] Exception type is appropriate (IncorrectArgument, PreconditionError, NotImplemented, ParsingError, AmbiguousDescription, ExtensionError, SolverFailure) +- [ ] Error message is clear and specific +- [ ] `got` and `expected` fields provided when applicable +- [ ] Actionable `suggestion` provided +- [ ] `context` provided for complex errors +- [ ] Exception is tested with `@test_throws` +- [ ] Error message is user-friendly (no jargon) +- [ ] Suggestion is concrete and actionable + +## Anti-Patterns + +```julia +# โŒ Generic errors +error("Something went wrong") + +# โŒ Missing context +throw(IncorrectArgument("Invalid value")) + +# โŒ No suggestions +throw(IncorrectArgument("Unknown constraint type", got=":boundary")) +``` + +## Related Skills + +- `testing-creation` skill โ€” exception testing patterns +- `docstrings` skill โ€” document exceptions in `# Throws` section +- `architecture` skill โ€” error handling architecture diff --git a/windsurf-skill-based/skills/exceptions/exception-types.md b/windsurf-skill-based/skills/exceptions/exception-types.md new file mode 100644 index 00000000..468d26ab --- /dev/null +++ b/windsurf-skill-based/skills/exceptions/exception-types.md @@ -0,0 +1,349 @@ +# Exception Types Reference + +CTBase provides seven exception types, all subtypes of `CTException`. Import with: + +```julia +import CTBase.Exceptions +``` + +Catch all domain errors uniformly with: + +```julia +try + risky_operation() +catch e + if e isa Exceptions.CTException + handle_error(e) + else + rethrow() + end +end +``` + +**Hierarchy:** + +```text +CTException (abstract) +โ”œโ”€โ”€ IncorrectArgument # Invalid argument value +โ”œโ”€โ”€ PreconditionError # Wrong order / state violation +โ”œโ”€โ”€ NotImplemented # Interface stub +โ”œโ”€โ”€ ParsingError # DSL / syntax error +โ”œโ”€โ”€ AmbiguousDescription # Description tuple not found +โ”œโ”€โ”€ ExtensionError # Missing optional dependency +โ””โ”€โ”€ SolverFailure # Solver / integrator failure +``` + +--- + +## 1. IncorrectArgument + +Use when an individual argument is invalid or violates a precondition. + +**Fields:** + +- `msg::String`: Main error message (required) +- `got::Union{String, Nothing}`: What value was received (optional) +- `expected::Union{String, Nothing}`: What value was expected (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(IncorrectArgument("Invalid criterion")) + +# With got/expected +throw(IncorrectArgument( + "Invalid criterion", + got=":invalid", + expected=":min or :max" +)) + +# Full context +throw(IncorrectArgument( + "Invalid criterion", + got=":invalid", + expected=":min or :max", + suggestion="Use objective!(ocp, :min, ...) or objective!(ocp, :max, ...)", + context="objective! function" +)) +``` + +**When to use:** + +- Invalid function arguments +- Type mismatches +- Value out of range +- Missing required parameters +- Invalid combinations of parameters + +--- + +## 2. PreconditionError + +Use when a function call violates a precondition or is not allowed in the current state of the system. The arguments may be valid, but the *timing* or *state* is wrong. + +**Fields:** + +- `msg::String`: Main error message (required) +- `reason::Union{String, Nothing}`: Why the precondition failed (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(PreconditionError("State must be set before dynamics")) + +# With reason and suggestion +throw(PreconditionError( + "Cannot call state! twice", + reason="state has already been defined for this OCP", + suggestion="Create a new OCP instance" +)) + +# Full context +throw(PreconditionError( + "Cannot modify frozen OCP", + reason="OCP has been finalized and is immutable", + suggestion="Create a new OCP or modify before calling finalize!()", + context="constraint! function" +)) +``` + +**When to use:** + +- Functions called in the wrong order +- Operations on uninitialized objects +- State machine violations +- Workflow step dependencies + +**Distinction from `IncorrectArgument`:** + +- `IncorrectArgument`: the *value* of an argument is wrong +- `PreconditionError`: the *timing* or *state* is wrong + +--- + +## 3. NotImplemented + +Use to mark interface points that must be implemented by concrete subtypes. + +**Fields:** + +- `msg::String`: Description of what is not implemented (required) +- `required_method::Union{String, Nothing}`: Method signature that needs implementation (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple message +throw(NotImplemented("solve! not implemented for MyStrategy")) + +# With required_method and suggestion +throw(NotImplemented( + "Method solve! not implemented", + required_method="solve!(::MyStrategy, ...)", + suggestion="Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" +)) + +# For abstract type contracts +abstract type AbstractStrategy end + +function solve!(strategy::AbstractStrategy, problem) + throw(NotImplemented( + "solve! must be implemented for each strategy type", + required_method="solve!(::$(typeof(strategy)), problem)", + suggestion="Define solve!(::$(typeof(strategy)), problem)", + context="strategy dispatch" + )) +end +``` + +**When to use:** + +- Abstract type interface methods +- Extension points +- Optional features not yet implemented +- Platform-specific functionality + +--- + +## 4. ParsingError + +Use for parsing errors in DSLs or structured input. + +**Fields:** + +- `msg::String`: Description of the parsing error (required) +- `location::Union{String, Nothing}`: Where in the input the error occurred (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) + +**Examples:** + +```julia +# Simple message +throw(ParsingError("Unexpected token 'end'")) + +# With location and suggestion +throw(ParsingError( + "Unexpected token 'end'", + location="line 42, column 15", + suggestion="Check syntax balance or remove extra 'end'" +)) +``` + +**When to use:** + +- DSL parsing errors +- Configuration file parsing +- Input validation during parsing +- Syntax errors + +--- + +## 5. AmbiguousDescription + +Use when a description (a tuple of `Symbol`s) cannot be matched to any known valid description in a catalogue. + +**Fields:** + +- `description::Tuple{Vararg{Symbol}}`: The ambiguous or incorrect description tuple (required) +- `candidates::Union{Vector{String}, Nothing}`: Suggested valid alternatives (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Constructor:** `AmbiguousDescription(description; msg=..., candidates=..., suggestion=..., context=...)` + +**Examples:** + +```julia +# Simple +throw(AmbiguousDescription((:f,))) + +# With candidates and suggestion +throw(AmbiguousDescription( + (:descent,), + candidates=["(:descent, :bfgs, :bisection)", "(:descent, :gradient, :fixedstep)"], + suggestion="Use a complete description like (:descent, :bfgs, :bisection)", + context="algorithm selection" +)) +``` + +**When to use:** + +- Description-based APIs where a partial `Symbol` tuple doesn't match any catalogue entry +- Algorithm selection via symbolic descriptions +- Pattern matching in mathematical modeling DSLs + +--- + +## 6. ExtensionError + +Use when a feature requires optional dependencies (weak dependencies) that are not loaded. + +**Fields:** + +- `weakdeps::Tuple{Vararg{Symbol}}`: Names of missing packages +- `feature::Union{String, Nothing}`: Which feature requires them (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Constructor:** `ExtensionError(pkgs::Symbol...; message="", feature=nothing, context=nothing)` + +โš ๏ธ `ExtensionError()` with no arguments throws `PreconditionError` instead. + +**Examples:** + +```julia +# Single missing dependency +throw(ExtensionError(:Plots)) + +# With feature description +throw(ExtensionError( + :Plots, + feature="result visualization", + context="plot_results function" +)) + +# Multiple missing dependencies +throw(ExtensionError(:SciMLBase, :OrdinaryDiffEq; + message="to integrate ODEs", + feature="ODE integration", + context="build_flow" +)) +``` + +**When to use:** + +- Extension stubs in `ext/` files (SciML, ForwardDiff, Plots, StaticArrays) +- Weak dependency not loaded by the user +- ODE integration, plotting, AD functionality behind extensions + +--- + +## 7. SolverFailure + +Use when a solver (ODE integrator, optimization solver, linear solver) fails to complete successfully. + +**Fields:** + +- `msg::String`: Error message describing the failure (required) +- `retcode::Union{String, Nothing}`: Solver-specific return code (optional) +- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) +- `context::Union{String, Nothing}`: Where the error occurred (optional) + +**Examples:** + +```julia +# Simple +throw(SolverFailure("ODE integration failed")) + +# With SciML retcode +throw(SolverFailure( + "ODE integration failed", + retcode=":Unstable", + suggestion="Reduce time step or check initial conditions", + context="SciML integrator in build_flow" +)) + +# Optimization solver +throw(SolverFailure( + "Optimization solver did not converge", + retcode=":MaxIterations", + suggestion="Increase max iterations or adjust tolerance settings", + context="IPOPT solver in CTDirect" +)) +``` + +**Common SciML return codes:** `:Unstable`, `:DtLessThanMin`, `:MaxIters`, `:Success` + +**When to use:** + +- ODE integration failures in CTFlows (SciML integrators) +- Non-convergence of optimization solvers +- Ill-conditioned linear systems +- Any numerical solver returning a failure status + +**Distinction from other exceptions:** + +- `IncorrectArgument`: the *input* is invalid +- `PreconditionError`: the *state* or *timing* is wrong +- `SolverFailure`: the *numerical computation* itself failed + +--- + +## Quick Reference + +| Situation | Exception | +| --- | --- | +| Invalid argument value | `IncorrectArgument` | +| Wrong function call order / state | `PreconditionError` | +| Unimplemented interface method | `NotImplemented` | +| DSL / syntax parsing error | `ParsingError` | +| Description tuple not matched | `AmbiguousDescription` | +| Missing optional dependency | `ExtensionError` | +| Solver / integrator failure | `SolverFailure` | diff --git a/windsurf-skill-based/skills/performance/SKILL.md b/windsurf-skill-based/skills/performance/SKILL.md new file mode 100644 index 00000000..ae50dadc --- /dev/null +++ b/windsurf-skill-based/skills/performance/SKILL.md @@ -0,0 +1,305 @@ +--- +name: performance +description: Julia performance standards for CTFlows: profiling workflow (Profile.jl, ProfileView.jl), benchmarking with BenchmarkTools.jl, allocation reduction (preallocate, views, in-place), common optimizations (@inbounds, @simd, StaticArrays, typed containers), parallelization, optimization workflow (measure-profile-optimize-verify). Invoke on hot paths, inner loops, or after type-stability work. +--- + +# Julia Performance and Type Stability Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "โšก **Applying Performance Rule**: [specific performance principle being applied]" + +This ensures transparency about which performance standard is being used and why. + +--- + +This document defines performance and type stability standards for the Control Toolbox project. Performance-critical code must follow these guidelines to ensure optimal execution speed and memory efficiency. + +## Core Principles + +1. **Measure First**: Profile before optimizing +2. **Focus on Hot Paths**: Optimize where it matters (inner loops, critical functions) +3. **Type Stability**: Ensure type-stable code (see `type-stability` skill) +4. **Avoid Premature Optimization**: Optimize only when necessary +5. **Maintain Readability**: Don't sacrifice clarity for marginal gains + +## Performance Hierarchy + +### Critical (Must Optimize) + +- Inner loops (called millions of times) +- Numerical computations in solvers +- Hot paths identified by profiling +- Real-time systems + +### Important (Should Optimize) + +- Frequently called functions +- Data processing pipelines +- API functions with performance requirements + +### Low Priority (Optimize if Easy) + +- One-time setup code +- User-facing convenience functions +- Error handling paths +- Debugging utilities + +## Profiling + +### Using Profile.jl + +```julia +using Profile + +@profile my_function(args...) +Profile.print() +Profile.clear() +@profile (for i in 1:1000; my_function(args...); end) +``` + +### Using ProfileView.jl + +```julia +using ProfileView + +@profview my_function(args...) +@profview for i in 1:1000 + my_function(args...) +end +``` + +**Interpreting Results:** +- **Red bars**: Hot spots (most time spent) +- **Wide bars**: Functions called many times +- **Type instabilities**: Yellow/red warnings + +## Benchmarking + +### Using BenchmarkTools.jl + +```julia +using BenchmarkTools + +@benchmark my_function($args...) + +b1 = @benchmark old_implementation($args...) +b2 = @benchmark new_implementation($args...) +judge(median(b2), median(b1)) +``` + +**Best Practices:** + +```julia +# โœ… Interpolate variables (avoids global variable penalty) +x = rand(1000) +@benchmark my_function($x) + +# โœ… Warm up before benchmarking +my_function(args...) +@benchmark my_function($args...) +``` + +## Memory Allocations + +### Reducing Allocations + +**โœ… Good - Preallocate buffers:** + +```julia +function process_data!(output, input) + for i in eachindex(input) + output[i] = input[i]^2 + end + return output +end + +output = similar(input) +process_data!(output, input) # No allocations +``` + +**โŒ Bad - Allocate in loop:** + +```julia +function process_data(input) + output = [] + for x in input + push!(output, x^2) # Allocates each iteration + end + return output +end +``` + +**โœ… Good - Use views instead of copies:** + +```julia +sub = @view matrix[1:10, :] # No allocation +``` + +**โœ… Good - In-place operations:** + +```julia +A .= B .+ C # In-place, no allocation +``` + +## Common Optimizations + +### 1. Avoid Global Variables + +```julia +# โŒ Bad +global_counter = 0 + +# โœ… Good +const COUNTER = Ref(0) +function increment() + COUNTER[] += 1 +end +``` + +### 2. Use @inbounds for Bounds-Checked Loops + +```julia +function sum_array(arr) + s = zero(eltype(arr)) + @inbounds for i in eachindex(arr) + s += arr[i] + end + return s +end +``` + +**โš ๏ธ Warning:** `@inbounds` disables bounds checking. Use only when safe. + +### 3. Use @simd for Vectorization + +```julia +function sum_array(arr) + s = zero(eltype(arr)) + @simd for i in eachindex(arr) + s += arr[i] + end + return s +end +``` + +### 4. Use StaticArrays for Small Arrays + +```julia +using StaticArrays + +v = SVector(1.0, 2.0, 3.0) +m = SMatrix{3,3}(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) +result = m * v # No allocation! +``` + +### 5. Avoid Untyped Containers + +```julia +# โŒ Bad +results = [] # Vector{Any} + +# โœ… Good +results = Float64[] +``` + +### 6. Use Multiple Dispatch Effectively + +```julia +function process(x) # Generic fallback +end + +function process(x::Float64) # Fast specialized method +end +``` + +## Performance Testing + +### Allocation Tests + +```julia +@testset "Allocations" begin + x = rand(1000) + allocs = @allocated process!(x) + @test allocs == 0 + + allocs = @allocated build_model(x) + @test allocs < 1000 # bytes +end +``` + +### Benchmark Tests + +```julia +@testset "Performance" begin + x = rand(1000) + b = @benchmark process($x) + @test median(b.times) < 1_000_000 # < 1ms + @test b.allocs == 0 +end +``` + +## Optimization Workflow + +1. **Profile** โ€” `@profview my_application()` +2. **Measure baseline** โ€” `baseline = @benchmark critical_function($args...)` +3. **Optimize** โ€” fix type instabilities, reduce allocations, use specialized algorithms +4. **Measure improvement** โ€” compare `median(baseline.times)` vs `median(optimized.times)` +5. **Verify correctness** โ€” `@test optimized_function(args...) โ‰ˆ baseline_function(args...)` + +## When NOT to Optimize + +**โŒ Don't optimize:** +- Before profiling +- Code that runs once +- Code that's already fast enough +- At the expense of readability + +## Parallelization + +### Using Threads + +```julia +using Base.Threads + +function parallel_sum(arr) + sums = zeros(nthreads()) + @threads for i in eachindex(arr) + sums[threadid()] += arr[i] + end + return sum(sums) +end +``` + +**Good candidates for parallelization:** independent computations, large data sets, CPU-bound tasks. + +## Quality Checklist + +Before finalizing performance optimizations: + +- [ ] Profiled to identify bottlenecks +- [ ] Benchmarked baseline performance +- [ ] Optimized critical paths only +- [ ] Verified type stability with `@inferred` +- [ ] Tested allocations are acceptable +- [ ] Verified correctness after optimization +- [ ] Maintained code readability +- [ ] Measured actual improvement + +## Tools Reference + +| Tool | Purpose | +|---|---| +| `Profile.jl` | Built-in profiling | +| `ProfileView.jl` | Visual profiling | +| `BenchmarkTools.jl` | Precise benchmarking | +| `@time` | Quick timing | +| `@allocated` | Allocation tracking | +| `@code_warntype` | Type stability | +| `StaticArrays.jl` | Fast small arrays | + +## Related Skills + +- `type-stability` skill โ€” type stability standards (critical for performance) +- `testing-creation` skill โ€” performance testing patterns +- `architecture` skill โ€” architecture patterns that affect performance diff --git a/windsurf-skill-based/skills/plan/SKILL.md b/windsurf-skill-based/skills/plan/SKILL.md new file mode 100644 index 00000000..f99f3ffd --- /dev/null +++ b/windsurf-skill-based/skills/plan/SKILL.md @@ -0,0 +1,270 @@ +--- +name: plan +description: Standards for writing implementation plans before coding in Julia/CTFlows: plan structure (title, what changes, dependency graph, branch step, numbered implementation steps, interleaved test checkpoints, docstring step, verification step, files summary), ordering rules, naming conventions, checklist. Invoke when creating or reviewing an implementation plan. +--- + +# Julia Implementation Plan Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "๐Ÿ“‹ **Applying Plan Rule**: [specific planning principle being applied]" + +This ensures transparency about which planning standard is being used and why. + +--- + +This document defines how to produce a complete, actionable implementation plan before writing any code. Plans are written first; implementation follows the plan step by step. **Docstrings are never written during implementation โ€” they are a dedicated final step.** + +> ๐Ÿ“Œ **Project-specific names** (package name, module DAG, test paths, branch convention): read `project.md`. Replace its content when adapting this skill to another package. + +--- + +## Core Principles + +1. **Plan before code** โ€” a plan must be fully written and reviewed before any file is touched. +2. **One step = one atomic change** โ€” each step modifies a single file or a single cohesive concern. Steps must be independently reviewable. +3. **Dependency-aware ordering** โ€” steps must respect the DAG of submodule dependencies (see `modules.md`). A file that depends on another always comes after it. +4. **Interleaved test checkpoints** โ€” instead of a massive test phase at the end, plans must include regular test checkpoints after logical groups of implementation steps. This provides validation before moving to the next phase. +5. **Docstrings are last** โ€” no docstring is written during implementation steps. A single dedicated step at the very end of the plan writes all docstrings at once, when the code is stable. +6. **No silent assumptions** โ€” any architectural decision made during planning must be stated explicitly in the plan (load-order changes, new exports, removed symbols, etc.). +7. **Skills are cited at the point of use** โ€” every step that triggers a skill from `.windsurf/skills/` must name that skill explicitly so the implementer knows which standard to follow. + +--- + +## Skills Reference + +The following skills from `.windsurf/skills/` govern implementation. The plan must mention each skill **at the step where it applies**, not just once globally. + +| Skill | Scope | Typical trigger in a plan | +|---|---|---| +| `architecture` | SOLID principles, patterns, module organisation | Any step that introduces a new type, new dependency, or restructures a module | +| `modules` (rule) | Submodule manifests, import style, qualification, export declarations | Any step touching a `<Name>.jl` manifest, import list, or `export` block | +| `exceptions` | Structured exceptions: `NotImplemented`, `IncorrectArgument`, `UnauthorizedCall`, `ParsingError` | Any step adding a stub, a contract method, or an error path | +| `testing-creation` | Test structure, fake types at top-level, unit/integration/contract/error separation | Every test step | +| `testing-execution` (rule) | Running tests, coverage reports | The verification step | +| `type-stability` | `@inferred`, parametric types, avoiding `Any` | Steps introducing new structs or performance-critical functions | +| `performance` | Profiling, benchmarking, allocation reduction | Steps on hot paths or after type-stability work | +| `docstrings` | Docstring templates, `$(TYPEDEF)`, `$(TYPEDSIGNATURES)`, cross-references | The dedicated docstring step only | +| `documentation` (rule) | `docs/` organisation, `make.jl`, API reference generation | If the plan includes a documentation update step | + +--- + +## Plan Structure + +A valid plan contains the following sections **in order**: + +### 1. Title and summary + +```markdown +# <Title of the refactor or feature> + +<One-paragraph summary: what changes, why it changes, and what the end state looks like.> +``` + +### 2. What changes and why + +Describe: + +- **What is being changed**: files, symbols, types, interfaces. +- **Why**: the architectural motivation (decoupling, new contract, load-order fix, etc.). +- **What disappears**: explicitly list every symbol, callable, or file that is deleted. +- **What is added**: new files, new types, new exports. + +### 3. Dependency graph after the change + +Always include a dependency diagram showing the new module/file relationships: + +``` +Module A โ†’ Module B, Module C +Module B โ†’ Module D +``` + +Use the same notation as the existing codebase. This graph drives step ordering in section 5. + +### 4. Branch step + +Always start with: + +```markdown +### Step 0 โ€” Branch + +\`\`\`bash +git checkout <base-branch> && git pull +git checkout -b <branch-name> +\`\`\` +``` + +**Note:** Use `develop` as the base branch if it exists, unless otherwise specified. + +### 5. Implementation steps + +Number every step starting from 1. Each step must follow this template: + +```markdown +### Step N โ€” `path/to/file.jl` [(new file) | (modified)] + +> ๐Ÿ“ Follow `architecture` skill โ€” [specific principle, e.g. "new abstract type follows the contract pattern"] +> ๐Ÿ—๏ธ Follow `modules` rule โ€” [specific rule, e.g. "add export at manifest end; use `using ..Sibling` for imports"] +> โš ๏ธ Follow `exceptions` skill โ€” [if stubs or error paths are added, e.g. "use `NotImplemented` with `required_method` and `suggestion` fields"] +> ๐Ÿ”ฌ Follow `type-stability` skill โ€” [if new parametric types or performance-critical functions are introduced] + +- <Bullet describing the first change in this file, naming exact symbols> +- <Bullet describing the second change> +- โ€ฆ + +> โ›” Do NOT write docstrings in this step. Leave existing docstrings untouched; +> new stubs get a single `# TODO: docstring` comment only. +``` + +Rules for step content: +- **One file per step** when the change is non-trivial. Multiple small related files (e.g. a manifest + one tiny helper) may share a step if they are always changed together. +- **Name the exact symbols** being added, removed, or renamed โ€” no vague language like "update the code". +- **Specify signatures** for new functions: `function build_problem(int::AbstractIntegrator, sys, config; variable)`. +- **Specify struct fields** for new types: `struct SciMLIntegrationResult{S<:SciMLBase.AbstractODESolution}`, field `sol::S`. +- **Call out load-order changes** explicitly when a `using` or `include` order changes in a manifest. +- **Call out export changes**: which symbols are added to or removed from `export`. + +### 6. Test checkpoints (Interleaved) + +After a logical group of implementation steps, add a dedicated test checkpoint. Continue numbering. Plans should have multiple test checkpoints interspersed rather than one big block at the end. + +```markdown +### Step N โ€” Test Checkpoint: <Subsystem/Phase> + +> ๐Ÿงช Follow `testing-creation` skill โ€” [specific rule, e.g. "define all fake structs at module top-level; separate unit/integration/contract/error testsets"] +> ๐Ÿ”ฌ Follow `type-stability` skill โ€” [if type-stability tests are needed for new symbols] +> โ–ถ๏ธ Follow `testing-execution` rule โ€” run targeted tests for this phase. + +- Define `struct Fake<X> <: <AbstractType>` at module top-level in `test/suite/<subdir>/test_<name>.jl` (never inside test functions). +- Implement the required contract methods on the fake. +- Test sections: + - `@testset "Contract: NotImplemented errors"` โ€” verify stubs throw correctly. + - `@testset "Functional: <describe>"` โ€” verify behaviour with fakes. + - `@testset "Exports"` โ€” verify new exports are present, deleted symbols are gone. + - `@testset "Type Stability"` โ€” `@inferred` checks on new performance-critical functions (if applicable). +- Run targeted tests: + \`\`\`bash + julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/<subdir>"])' 2>&1 | tee /tmp/phase_N.log + \`\`\` +``` + +Rules for test steps: +- Every new public symbol gets at least one test. +- Every deleted public symbol gets a regression test confirming it no longer exists. +- Fake types are defined at **module top-level** โ€” never inside test functions (`testing-creation` skill ยง1). +- Use fake subtypes for stub/extension testing, never real types (`testing-creation` skill ยง6). +- No extension package imports (e.g. `import SciMLBase`) in files that test pure-Julia contracts. +- A new `test_decoupling.jl` file is added whenever an architectural decoupling is claimed. + +### 7. Docstring step (always last implementation activity) + +```markdown +### Step N โ€” Docstrings (all modified files) + +> ๐Ÿ“š Follow `docstrings` skill โ€” apply `$(TYPEDEF)` / `$(TYPEDSIGNATURES)`, full sections +> (`# Arguments`, `# Returns`, `# Throws`, `# Example`), `[@ref]` / `[@extref]` cross-references, +> and safe runnable examples only. + +Write or update docstrings for every new or changed public symbol: +- `src/X/y.jl` โ€” `AbstractFoo`, `bar`, `baz` +- `src/Y/z.jl` โ€” `build_something`, `SomeResult` +- `ext/MyExt.jl` โ€” `ConcreteResult`, accessor implementations +- โ€ฆ +``` + +If the plan also modifies `docs/`, add: + +```markdown +> ๐Ÿ“– Follow `documentation` rule โ€” update `docs/api_reference.jl` and `docs/make.jl` if new submodules +> or extensions are introduced; add/remove entries in `automatic_reference_documentation` calls. +``` + +### 8. Verification step (always final) + +```markdown +### Step N โ€” Run tests + +> โ–ถ๏ธ Follow `testing-execution` rule โ€” standard test command below. + +\`\`\`bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/<branch_name>.log +grep -E "Error|Fail|Test Summary" /tmp/<branch_name>.log +\`\`\` + +Expected: all test suites pass, zero failures, zero errors. +``` + +### 9. Files summary + +Close the plan with a compact table: + +```markdown +## Files summary + +**New**: `src/X/y.jl`, `test/suite/z/test_w.jl` + +**Modified**: +- `src/A/A.jl` โ€” load order, exports (`modules` rule) +- `src/B/b.jl` โ€” signature change (`architecture` skill, `exceptions` skill) +- โ€ฆ + +**Deleted**: (none) | `src/old_file.jl` +``` + +--- + +## Ordering Rules + +Apply these rules in order when sequencing steps: + +1. **Lower-level modules before higher-level modules** โ€” respect the dependency DAG from the `modules` rule. If `Solutions` is used by `Flows`, all `Solutions` steps come before `Flows` steps. +2. **Manifests after the files they include** โ€” when a manifest's `include` list or `export` list changes, the step for the manifest comes after the steps for the included files. +3. **Extensions after core modules** โ€” extension files (`ext/`) always follow the core module files they extend. +4. **Test checkpoints interleaved** โ€” place test checkpoints immediately after the implementation steps of a logical phase, before moving to the next implementation phase. +5. **Docstring step after all test steps** โ€” docstrings are always the last implementation activity. +6. **Verification step is always the final step**. + +--- + +## Naming Conventions for Steps + +| Situation | Step title pattern | +|---|---| +| New file | `Step N โ€” \`path/to/file.jl\` (new file)` | +| Modified file | `Step N โ€” \`path/to/file.jl\`` | +| Group of manifest + small helpers | `Step N โ€” \`src/X/X.jl\` + \`src/X/helpers.jl\`` | +| New test file | `Step N โ€” \`test/suite/x/test_y.jl\` (new file)` | +| Modified test file | `Step N โ€” \`test/suite/x/test_y.jl\`` | +| Test Checkpoint | `Step N โ€” Test Checkpoint: <Subsystem>` | +| Docstring phase | `Step N โ€” Docstrings (all modified files)` | +| Verification | `Step N โ€” Run tests` | + +--- + +## What a Plan Must NOT Contain + +- โŒ Any actual Julia code written inline in the plan body (code blocks showing *signatures* and *shapes* are fine; full implementations are not). +- โŒ Docstrings for new symbols (defer to the docstring step). +- โŒ Vague change descriptions ("update the function", "fix the imports") โ€” always name the exact symbol. +- โŒ Steps that mix implementation and testing in the same step (except trivially small changes). +- โŒ Steps that skip the load-order / export impact of a change. +- โŒ A test step that uses `isdefined(Module, :SomePkg)` as a decoupling check โ€” use fake subtypes instead (`testing-creation` skill ยง6). +- โŒ A step that omits which `.windsurf/skills/` skill (or rule) applies to it. + +--- + +## Checklist Before Handing the Plan to the Implementer + +- [ ] Title, summary, and "What changes and why" are present and accurate. +- [ ] Dependency graph is drawn and consistent with the `modules` rule. +- [ ] Every step modifies exactly one file (or a justified small group). +- [ ] Steps are ordered correctly per the DAG and ordering rules above. +- [ ] Every step cites the relevant skill(s) or rule(s) explicitly. +- [ ] Every new public symbol has a corresponding test step. +- [ ] Every deleted public symbol has a regression test step. +- [ ] No docstrings are written before the dedicated docstring step. +- [ ] The docstring step names every file and every symbol that needs a docstring, and cites the `docstrings` skill. +- [ ] If `docs/` is modified, the docstring step also cites the `documentation` rule. +- [ ] The verification step is present, cites the `testing-execution` rule, and uses `Pkg.test()`. +- [ ] The files summary lists new, modified, and deleted files, with the relevant skill/rule beside each entry. +- [ ] Architectural decisions (load-order changes, export additions/removals) are stated explicitly. diff --git a/windsurf-skill-based/skills/plan/project.md b/windsurf-skill-based/skills/plan/project.md new file mode 100644 index 00000000..effcb136 --- /dev/null +++ b/windsurf-skill-based/skills/plan/project.md @@ -0,0 +1,70 @@ +# Project: CTFlows + +> **Adaptation guide** โ€” This file contains everything that changes between packages in the control-toolbox ecosystem. +> When copying this skill to another package, only edit this file (and the `description` field in `SKILL.md`). + +--- + +## Package + +- **Name**: `CTFlows` +- **Base branch**: `develop` +- **Test entry point**: `test/runtests.jl` +- **Test subdirectory**: `test/suite/<subdir>/test_<name>.jl` +- **Extension directory**: `ext/` + +## Module Dependency DAG + +```text +Common + โ†“ +Data + โ†“ +Systems + โ†“ +Integrators + โ†“ +Flows + โ†“ +Solutions +``` + +Extensions (`ext/`) always come after the core modules they extend. + +## Submodule List + +| Submodule | Source file | Purpose | +| --- | --- | --- | +| `Common` | `src/Common/Common.jl` | AbstractTag, AbstractTrait, traits, ODE params | +| `Data` | `src/Data/Data.jl` | VectorField, HamiltonianVectorField | +| `Systems` | `src/Systems/Systems.jl` | AbstractSystem subtypes | +| `Integrators` | `src/Integrators/Integrators.jl` | AbstractIntegrator, result types | +| `Flows` | `src/Flows/Flows.jl` | AbstractFlow, Flow, MultiPhaseFlow | +| `Solutions` | `src/Solutions/Solutions.jl` | Solution building and accessors | + +## Test Subdirectory Map + +| Module(s) under test | Test subdirectory | +| --- | --- | +| Common | `suite/common/` | +| Data | `suite/data/` | +| Systems | `suite/systems/` | +| Integrators | `suite/integrators/` | +| Flows | `suite/flows/` | +| MultiPhaseFlow | `suite/multiphase/` | +| Solutions | `suite/solutions/` | +| Extensions | `suite/extensions/` | +| Aqua.jl / meta | `suite/meta/` | + +## Test Run Command + +```bash +julia --project -e 'using Pkg; Pkg.test("CTFlows"; test_args=["suite/<subdir>"])' 2>&1 | tee /tmp/<branch>.log +``` + +Full suite: + +```bash +julia --project -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/<branch>.log +grep -E "Error|Fail|Test Summary" /tmp/<branch>.log +``` diff --git a/windsurf-skill-based/skills/testing-creation/SKILL.md b/windsurf-skill-based/skills/testing-creation/SKILL.md new file mode 100644 index 00000000..9c3d62e9 --- /dev/null +++ b/windsurf-skill-based/skills/testing-creation/SKILL.md @@ -0,0 +1,254 @@ +--- +name: testing-creation +description: Julia testing standards for CTFlows: contract-first testing, test organization under test/suite/, fake structs defined at module top-level (never inside test functions), unit/integration/contract/error test separation, import qualification rules, export verification, extension stub testing with fake types. Invoke when writing or reviewing test files. +--- + +# Julia Testing Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "๐Ÿงช **Applying Testing Rule**: [specific testing principle being applied]" + +This ensures transparency about which testing standard is being used and why. + +--- + +This document defines the testing standards for the CTFlows.jl project. All Julia code modifications must be accompanied by appropriate tests following these guidelines. + +> ๐Ÿ“Œ **Project-specific names** (module names, submodule list, test directory structure, import style, test constants): read `project.md`. Replace its content when adapting this skill to another package. + +## Core Principles + +1. **Contract-First Testing**: Define and test contracts (interfaces) first using stubs/mocks to verify correct routing and behavior. Test both public APIs and internal functions when they implement important logic. +2. **Orthogonality**: Tests are independent from source code structure (test organization โ‰  src organization) +3. **Isolation**: Unit tests use mocks/fakes to isolate components; integration tests verify interactions +4. **Determinism**: Tests must be reproducible and not depend on external state +5. **Clarity**: Test intent must be immediately obvious from test names and structure + +## Test Organization + +### Directory Structure + +Tests are organized under `test/suite/` by **functionality**, not by source file structure: + +- `suite/common/`: Common types tests (AbstractTag, AbstractTrait, configs, traits, ODE parameters) +- `suite/data/`: Data types tests (AbstractVectorField, HamiltonianVectorField, VectorField) +- `suite/flows/`: Flow types tests (AbstractFlow, Flow, building, calling, callables) +- `suite/integrators/`: Integrator tests (AbstractIntegrator, building, SciML, IntegrationResult) +- `suite/extensions/`: Extension tests (SciML, ForwardDiff, Plots, StaticArrays) +- `suite/multiphase/`: MultiPhase flow tests (concatenation, calling) +- `suite/solutions/`: Solution building tests +- `suite/systems/`: System types tests (AbstractSystem subtypes) +- `suite/meta/`: Meta tests (Aqua.jl quality checks) + +### File and Function Naming + +**Required pattern:** + +- File name: `test_<name>.jl` +- Entry function: `test_<name>()` (matching the filename exactly) + +**Example:** + +```julia +# File: test/suite/flows/test_abstract_flow.jl +module TestAbstractFlow + +import Test +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTFlows.Data +import CTSolvers: CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_abstract_flow() + Test.@testset "Abstract Flow Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + # Tests here + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_flow() = TestAbstractFlow.test_abstract_flow() +``` + +## Test Structure + +### Test Categories (โ†’ read `test-categories.md`) + +Four categories: Unit, Integration, Contract, Error โ€” with vocabulary (fake / stub) and the `====` separator pattern. Read `test-categories.md`. + +## Critical Rules (โ†’ read `critical-rules.md`) + +Six rules to apply when writing any test file. Read `critical-rules.md` for full detail and examples. + +1. **Struct definitions at top-level** โ€” never inside test functions (world-age issues) +2. **`import` not `using`** โ€” qualify all calls (`Flows.build_flow`, `Exceptions.NotImplemented`) +3. **Export verification** โ€” test that public symbols are defined, internals are not re-exported +4. **Internal functions** โ€” test `_`-prefixed functions directly when logic is complex +5. **Test independence** โ€” create fresh instances in each testset, no shared mutable state +6. **Stub testing** โ€” interface stubs (`NotImplemented`): fake omits the method; extension stubs (`ExtensionError`): always fake types, never real types (avoids load-order failures) + +## Test Quality Standards + +### Assertion Quality + +**Use specific assertions:** + +**โœ… Good:** + +```julia +Test.@test result โ‰ˆ 1.23 atol=1e-10 +Test.@test obj isa Systems.AbstractSystem +Test.@test length(components) == 2 +Test.@test status == :first_order +``` + +**โŒ Poor:** + +```julia +Test.@test result > 0 # Too vague +Test.@test obj != nothing # Use Test.@test !isnothing(obj) +Test.@test true # Meaningless +``` + +### Test Naming + +Test names should describe **what** is being tested, not **how**: + +**โœ… Good:** + +```julia +Test.@testset "System construction" +Test.@testset "Contract Implementation - NotImplemented errors" +Test.@testset "Complete workflow - flow building" +``` + +**โŒ Poor:** + +```julia +Test.@testset "Test 1" +Test.@testset "Builder" +Test.@testset "Check stuff" +``` + +## Coverage Requirements + +### What to Test + +**Must test:** + +- โœ… Public API functions and types +- โœ… Contract implementations +- โœ… Error paths and exception handling +- โœ… Edge cases (empty inputs, boundary values, special cases) +- โœ… Type stability (for performance-critical code) +- โœ… Integration between components + +**Should test:** + +- โš ๏ธ Internal functions with complex logic +- โš ๏ธ Validation logic +- โš ๏ธ Conversion and transformation functions + +**Don't test:** + +- โŒ Trivial getters/setters without logic +- โŒ External library behavior +- โŒ Generated code (unless custom logic added) + +### Performance and Type Stability Tests + +For performance-critical code, add type stability and allocation tests. + +**See also:** `type-stability` skill for comprehensive standards. + +#### Type Stability Tests + +`@inferred` only works on **function calls**, not direct field access: + +```julia +Test.@testset "Type Stability" begin + sys = FakeSystem(2) + flow = build_test_flow(sys) # helper defined at module top-level + + Test.@test_nowarn Test.@inferred Flows.system(flow) + Test.@test_nowarn Test.@inferred Integrators.integrate(integrator, sys, t0, x0, tf) + + # โŒ WRONG: @inferred on field access + # Test.@inferred flow.system # ERROR! + + # โœ… CORRECT: wrap in a function call + Test.@test_nowarn Test.@inferred Flows.system(flow) +end +``` + +#### Allocation Tests + +```julia +Test.@testset "Allocations" begin + sys = FakeSystem(2) + allocs = Test.@allocated Systems.state_dimension(sys) + Test.@test allocs == 0 +end +``` + +## Anti-Patterns to Avoid + +### โŒ Don't: Test implementation details + +```julia +# BAD: Testing internal field names +Test.@test obj._internal_cache == something +``` + +### โŒ Don't: Use global mutable state + +```julia +# BAD: Global state between tests +const GLOBAL_COUNTER = Ref(0) + +Test.@testset "Test A" begin + GLOBAL_COUNTER[] += 1 # Affects other tests! +end +``` + +### โŒ Don't: Depend on test execution order + +```julia +# BAD: Test B depends on Test A running first +Test.@testset "Test A" begin + global shared_data = compute_something() +end + +Test.@testset "Test B" begin + Test.@test shared_data > 0 # Breaks if A doesn't run first! +end +``` + +## Quality Checklist + +Before finalizing tests, verify: + +- [ ] All structs defined at module top-level +- [ ] Unit and integration tests clearly separated +- [ ] Method calls are qualified (e.g., `Systems.function_name`) +- [ ] Test names describe what is being tested +- [ ] Each test is independent and deterministic +- [ ] Error cases are tested with `@test_throws` +- [ ] No file I/O or external dependencies in unit tests +- [ ] Fake types implement minimal contracts +- [ ] Tests document non-obvious logic +- [ ] No global mutable state +- [ ] Tests pass locally before committing + +## References + +- Test execution: `testing-execution` rule (`.windsurf/rules/testing-execution.md`) +- Test runner entry point: `test/runtests.jl` diff --git a/windsurf-skill-based/skills/testing-creation/critical-rules.md b/windsurf-skill-based/skills/testing-creation/critical-rules.md new file mode 100644 index 00000000..f384ee45 --- /dev/null +++ b/windsurf-skill-based/skills/testing-creation/critical-rules.md @@ -0,0 +1,213 @@ +# Critical Rules + +## Rule 1 โ€” Struct Definitions at Top-Level + +**NEVER define `struct`s inside test functions.** All helper types, mocks, and fakes must be defined at the **module top-level**. + +**โŒ Wrong:** + +```julia +function test_something() + Test.@testset "Test" begin + struct FakeType end # WRONG! Causes world-age issues + end +end +``` + +**โœ… Correct:** + +```julia +module TestSomething + +# TOP-LEVEL: Define all structs here +struct FakeType end + +function test_something() + Test.@testset "Test" begin + obj = FakeType() # Correct + end +end + +end # module +``` + +--- + +## Rule 2 โ€” Import and Qualification Rules + +**Use `import` instead of `using`** to avoid namespace pollution: + +```julia +import Test +import CTBase.Exceptions +import CTFlows.Common +import CTFlows.Data +import CTFlows.Systems +import CTFlows.Flows +import CTFlows.Integrators +import CTSolvers: CTSolvers +``` + +**Always qualify method calls**, omitting the root module for submodules: + +**โœ… Correct:** + +```julia +Test.@test_throws Exceptions.IncorrectArgument invalid_call() +Test.@test Flows.system(flow) isa Systems.AbstractSystem +Test.@test Integrators.integrate(integrator, sys, t0, x0, tf) isa Integrators.AbstractIntegrationResult +``` + +**โŒ Wrong:** + +```julia +Test.@test_throws CTBase.Exceptions.IncorrectArgument invalid_call() # Too verbose +Test.@test CTFlows.Flows.system(flow) isa CTFlows.Systems.AbstractSystem # Too verbose +Test.@test true # Ambiguous +``` + +**Why:** Explicit qualification makes test intent clear while avoiding excessive verbosity. + +--- + +## Rule 3 โ€” Export Verification + +Add dedicated tests to verify exports and internal symbols: + +```julia +Test.@testset "Exports Verification" begin + Test.@testset "Submodule exports" begin + for sym in (:AbstractFlow, :Flow, :build_flow) + Test.@test isdefined(Flows, sym) + end + end + + Test.@testset "Package-level non-exports" begin + for sym in (:_internal_helper,) + Test.@test isdefined(Flows, sym) # exists in submodule + Test.@test !isdefined(CTFlows, sym) # not re-exported at package level + end + end +end +``` + +--- + +## Rule 4 โ€” Testing Internal Functions + +**Internal functions (prefixed with `_`) should be tested** when they contain significant logic. + +**Direct testing** โ€” preferred when logic is complex or has multiple branches: + +```julia +Test.@testset "Internal Function Tests" begin + result = Flows._validate_flow(flow, config) + Test.@test result isa Bool + Test.@test result == true +end +``` + +**Indirect testing** โ€” acceptable when logic is simple or already covered by integration tests: + +```julia +Test.@testset "build_flow - validation" begin + flow = Flows.build_flow(sys, integrator) + Test.@test Flows.system(flow) isa Systems.AbstractSystem +end +``` + +**When to test directly:** + +- Complex logic with multiple branches +- Error handling paths +- Edge cases hard to trigger via public API + +**When to test indirectly:** + +- Simple delegation or data transformation +- Logic already covered by integration tests +- Implementation details likely to change + +--- + +## Rule 5 โ€” Test Independence + +Each test must be independent and not rely on execution order. Create fresh instances inside each testset: + +**โœ… Correct:** + +```julia +Test.@testset "Test A" begin + flow = Flows.build_flow(FakeSystem(2), FakeIntegrator()) + # Test A logic +end + +Test.@testset "Test B" begin + flow = Flows.build_flow(FakeSystem(2), FakeIntegrator()) # Fresh instance + # Test B logic +end +``` + +**โŒ Wrong:** + +```julia +flow = Flows.build_flow(...) # Shared state + +Test.@testset "Test A" begin + # Modifies flow โ€” affects Test B! +end +``` + +--- + +## Rule 6 โ€” Stub Testing + +Two kinds of stubs exist in CTFlows; each requires a different approach. + +### 6a. Interface Stubs (`NotImplemented`) + +An abstract type's default method throws `NotImplemented` when a concrete subtype has not provided an implementation. Test this by creating a **fake that omits the required method**. + +```julia +# TOP-LEVEL: fake that deliberately does NOT implement integrate +struct StubIntegrator <: Integrators.AbstractIntegrator end + +Test.@testset "Interface stubs" begin + Test.@testset "integrate stub throws NotImplemented" begin + stub = StubIntegrator() + Test.@test_throws Exceptions.NotImplemented Integrators.integrate(stub, sys, t0, x0, tf) + end +end +``` + +This test is safe regardless of which extensions are loaded. + +### 6b. Extension Stubs (`ExtensionError` / fallback behavior) + +Extension code registers implementations for **known** types. When called with an **unknown** type, the stub (fallback) returns `missing` or throws `ExtensionError`. **Always use a fake type** that no extension knows about. + +**Why:** If a real type is used (e.g., `Integrators.SciML`) and the extension is loaded by another test file, the stub is replaced by the real implementation โ€” the test silently passes or fails for the wrong reason. + +```julia +# TOP-LEVEL: fake tag โ€” no extension registers an impl for this +struct FakeExtTag <: Common.AbstractTag end + +Test.@testset "Extension stubs" begin + Test.@testset "unknown tag โ†’ fallback returns missing" begin + result = Integrators.__default_sciml_algorithm(FakeExtTag) + Test.@test result === missing + end +end +``` + +**โŒ Wrong โ€” real type, load-order dependent:** + +```julia +# FAILS when CTFlowsSciML is loaded elsewhere in the suite +Test.@test_throws Exceptions.ExtensionError Integrators.SciML() +``` + +**Allowed with real types:** + +- Type hierarchy checks: `Test.@test Integrators.SciMLIntegrator <: Integrators.AbstractIntegrator` +- Pure metadata methods that don't depend on extension state (`id`, `description`) diff --git a/windsurf-skill-based/skills/testing-creation/project.md b/windsurf-skill-based/skills/testing-creation/project.md new file mode 100644 index 00000000..86cf7714 --- /dev/null +++ b/windsurf-skill-based/skills/testing-creation/project.md @@ -0,0 +1,93 @@ +# Project: CTFlows + +> **Adaptation guide** โ€” This file contains everything that changes between packages in the control-toolbox ecosystem. +> When copying this skill to another package, only edit this file (and the `description` field in `SKILL.md`). + +--- + +## Package + +```julia +CTFlows +``` + +## Submodules + +| Submodule | Content | +| --- | --- | +| `Common` | `AbstractTag`, `AbstractTrait`, configs, traits, ODE parameters | +| `Data` | `AbstractVectorField`, `HamiltonianVectorField`, `VectorField` | +| `Systems` | `AbstractSystem` subtypes | +| `Flows` | `AbstractFlow`, `Flow`, `MultiPhaseFlow`, building, calling | +| `Integrators` | `AbstractIntegrator`, `AbstractIntegrationResult`, building | +| `Solutions` | Solution building and accessors | + +## Import Style + +Use the `import X: X` qualified form so the submodule name is in scope: + +```julia +import CTBase.Exceptions: Exceptions +import CTFlows: CTFlows +import CTFlows.Common: Common +import CTFlows.Data: Data +import CTFlows.Systems: Systems +import CTFlows.Flows: Flows +import CTFlows.Integrators: Integrators +import CTFlows.Solutions: Solutions +import CTSolvers.Strategies: Strategies +import CTSolvers.Options: Options +``` + +For extension test files that load SciML or StaticArrays, also add: + +```julia +using SciMLBase: SciMLBase, ODEProblem +using OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5, Tsit5 +import StaticArrays: SA +``` + +## Test Directory Structure + +```text +test/suite/ +โ”œโ”€โ”€ common/ # AbstractTag, AbstractTrait, configs, traits, ODE parameters +โ”œโ”€โ”€ data/ # AbstractVectorField, HamiltonianVectorField, VectorField +โ”œโ”€โ”€ flows/ # AbstractFlow, Flow, building, calling, callables +โ”œโ”€โ”€ integrators/ # AbstractIntegrator, building, SciML, IntegrationResult +โ”œโ”€โ”€ extensions/ # SciML, ForwardDiff, Plots, StaticArrays +โ”œโ”€โ”€ multiphase/ # MultiPhase flow tests (concatenation, calling) +โ”œโ”€โ”€ solutions/ # Solution building +โ”œโ”€โ”€ systems/ # AbstractSystem subtypes +โ””โ”€โ”€ meta/ # Aqua.jl quality checks +``` + +## Test Constants + +Every test file defines these constants at module level: + +```julia +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +``` + +## Test Entry Point Pattern + +```julia +# Inside the module +function test_<name>() + Test.@testset "<Description>" verbose=VERBOSE showtiming=SHOWTIMING begin + # โ€ฆ + end +end + +# CRITICAL: redefine in outer scope so the test runner can call it +test_<name>() = Test<Name>.test_<name>() +``` + +## Extension Access Pattern (for extension test files) + +```julia +const CTFlowsSciML = Base.get_extension(CTFlows, :CTFlowsSciML) +const CTFlowsOrdinaryDiffEqTsit5 = Base.get_extension(CTFlows, :CTFlowsOrdinaryDiffEqTsit5) +``` diff --git a/windsurf-skill-based/skills/testing-creation/test-categories.md b/windsurf-skill-based/skills/testing-creation/test-categories.md new file mode 100644 index 00000000..fed742de --- /dev/null +++ b/windsurf-skill-based/skills/testing-creation/test-categories.md @@ -0,0 +1,180 @@ +# Test Categories + +**Vocabulary used in this document:** + +- **Fake** โ€” minimal struct that implements the required contract methods; used to isolate the component under test. +- **Stub** โ€” default method on an abstract type that throws `NotImplemented` or `ExtensionError`; tested by calling it on a fake type. +- **Mock** (interaction-recording) โ€” not used in CTFlows; fakes are sufficient. + +## 1. Unit Tests + +**Purpose**: Test single functions/components in isolation. + +**Characteristics:** + +- Pure logic, deterministic +- Use fake structs to isolate behavior +- No file I/O, network, or external dependencies +- Fast execution (<1ms per test) + +**Example:** + +```julia +Test.@testset "UNIT TESTS - Flow Types" begin + Test.@testset "Flow construction" begin + flow = Flows.Flow(fake_system, fake_integrator) + Test.@test flow isa Flows.Flow + Test.@test flow isa Flows.AbstractFlow + end +end +``` + +--- + +## 2. Integration Tests + +**Purpose**: Test interaction between multiple components through a complete workflow. + +**Characteristics:** + +- Exercise several real module boundaries together +- Use fakes only for leaf dependencies (not for the component chain being tested) +- Slower execution (acceptable up to 1s per test) + +**Example:** + +```julia +Test.@testset "INTEGRATION TESTS" begin + Test.@testset "flow building and system access" begin + sys = FakeSystem(2) # fake leaf dependency + integrator = FakeIntegrator() + + # build_flow exercises Flows + Systems + Integrators together + flow = Flows.build_flow(sys, integrator) + Test.@test flow isa Flows.AbstractFlow + Test.@test Flows.system(flow) isa Systems.AbstractSystem + Test.@test Flows.integrator(flow) isa Integrators.AbstractIntegrator + end +end +``` + +--- + +## 3. Contract Tests + +**Purpose**: Verify API contracts using fake implementations. + +**Characteristics:** + +- Define minimal fake types at top-level (never inside test functions) +- Implement only required contract methods +- Test routing, defaults, and error paths +- Verify Liskov Substitution Principle + +**Example:** + +```julia +# TOP-LEVEL: Fake type for contract testing +struct FakeSystem <: Systems.AbstractStateSystem{Common.Autonomous, Common.Fixed} + state_dim::Int +end + +# Implement contract +Systems.rhs(sys::FakeSystem) = (du, u, p, t) -> nothing +Systems.state_dimension(sys::FakeSystem) = sys.state_dim + +# Test contract +Test.@testset "Contract Implementation" begin + sys = FakeSystem(2) + Test.@test Systems.state_dimension(sys) == 2 +end +``` + +--- + +## 4. Error Tests + +**Purpose**: Verify that stubs and error paths throw the right exception with a useful message. + +Two sub-cases must be distinguished: + +### 4a. Interface Stubs (`NotImplemented`) + +A fake that inherits from an abstract type but does **not** implement a required contract method will trigger the abstract type's stub, which throws `NotImplemented`. + +```julia +# TOP-LEVEL: fake that intentionally omits the required method +struct StubIntegrator <: Integrators.AbstractIntegrator end +# (no Integrators.integrate method defined for StubIntegrator) + +Test.@testset "Interface stubs" begin + Test.@testset "integrate throws NotImplemented" begin + stub = StubIntegrator() + Test.@test_throws Exceptions.NotImplemented Integrators.integrate(stub, sys, t0, x0, tf) + end +end +``` + +### 4b. Extension Stubs (`ExtensionError`) + +Extension stubs throw `ExtensionError` when the required weak dependency is missing. **Always use a fake type** โ€” never a real type โ€” so that the test is independent of whether another test file happens to load the extension. + +```julia +# TOP-LEVEL: fake tag for which no extension registers an implementation +struct FakeExtTag <: Common.AbstractTag end + +Test.@testset "Extension stubs" begin + Test.@testset "unknown tag returns missing (fallback behavior)" begin + result = Integrators.__default_sciml_algorithm(FakeExtTag) + Test.@test result === missing + end +end +``` + +> **Why fake types?** If a real type is used and the extension is loaded (by any other test in the suite), the stub is replaced by the real implementation and the test silently passes or fails for the wrong reason. + +--- + +## Separation Pattern + +Use section comments to visually separate categories within a single testset: + +```julia +function test_flow_components() + Test.@testset "Flow Components" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + # Pure unit tests + end + + # ==================================================================== + # CONTRACT TESTS + # ==================================================================== + + Test.@testset "Contract Implementation" begin + # Contract tests with fakes + end + + # ==================================================================== + # INTEGRATION TESTS + # ==================================================================== + + Test.@testset "Integration" begin + # Multi-component tests + end + + # ==================================================================== + # ERROR TESTS + # ==================================================================== + + Test.@testset "Error Cases" begin + # Exception and edge-case tests + end + + end +end +``` diff --git a/windsurf-skill-based/skills/type-stability/SKILL.md b/windsurf-skill-based/skills/type-stability/SKILL.md new file mode 100644 index 00000000..7f3aa8ab --- /dev/null +++ b/windsurf-skill-based/skills/type-stability/SKILL.md @@ -0,0 +1,292 @@ +--- +name: type-stability +description: Julia type stability standards for CTFlows: what type stability means, testing with @inferred and @code_warntype, parametric types vs abstract fields, NamedTuple vs Dict, avoiding Any in hot paths, conditional return types, global variables, function barriers, fixing type instabilities. Invoke when introducing new structs, parametric types, or performance-critical functions. +--- + +# Julia Type Stability Standards + +## ๐Ÿค– **Agent Directive** + +**When applying this skill, explicitly state**: "๐Ÿ”ง **Applying Type Stability Rule**: [specific type stability principle being applied]" + +This ensures transparency about which type stability standard is being used and why. + +--- + +This document defines type stability standards for the Control Toolbox project. Type stability is crucial for Julia performance and must be carefully considered in performance-critical code paths. + +## Core Principles + +1. **Type Inference**: The compiler must be able to determine return types from input types +2. **Performance**: Type-stable code is typically 10-100x faster than type-unstable code +3. **Testability**: Type stability must be verified with `@inferred` tests +4. **Clarity**: Type-stable code is often clearer and more maintainable + +## What is Type Stability? + +A function is **type-stable** if the type of its return value can be inferred from the types of its inputs at compile time. + +```julia +# โœ… Type-stable: return type is always Int +function get_dimension(ocp::OptimalControlProblem)::Int + return ocp.state_dimension +end + +# โŒ Type-unstable: return type depends on runtime value +function get_value(dict::Dict{Symbol, Any}, key::Symbol) + return dict[key] # Could be Int, Float64, String, anything! +end +``` + +## Testing Type Stability + +### Using `@inferred` + +```julia +@testset "Type Stability" begin + ocp = create_test_ocp() + + @test_nowarn @inferred get_dimension(ocp) + @test_nowarn @inferred state_dimension(ocp) + @test_nowarn @inferred process_constraint(ocp, :initial) +end +``` + +### Common Mistake: Testing Non-Functions + +```julia +# โŒ WRONG: @inferred on field access +@inferred ocp.state_dimension # ERROR: Not a function call! + +# โœ… CORRECT: Wrap in a function +function get_state_dim(ocp) + return ocp.state_dimension +end +@inferred get_state_dim(ocp) +``` + +### Using `@code_warntype` + +```julia +julia> @code_warntype get_value(dict, :key) +# Look for red "Any" or yellow warnings in the output +``` + +**What to look for:** +- Red `Any` or `Union{...}` in return type +- Yellow warnings about type instabilities +- Multiple possible return types + +## Type-Stable Structures + +### Use Parametric Types + +**โŒ Type-Unstable:** + +```julia +struct OptionDefinition + name::Symbol + type::Type + default::Any # Type-unstable! +end +``` + +**โœ… Type-Stable:** + +```julia +struct OptionDefinition{T} + name::Symbol + type::Type{T} + default::T # Type-stable! +end + +function get_default(opt::OptionDefinition{T}) where T + return opt.default # Return type: T +end +``` + +### Use NamedTuple Instead of Dict + +**โŒ Type-Unstable:** + +```julia +struct StrategyMetadata + specs::Dict{Symbol, OptionDefinition} # Values have unknown types +end +``` + +**โœ… Type-Stable:** + +```julia +struct StrategyMetadata{NT <: NamedTuple} + specs::NT # Type-stable with known keys +end +``` + +### Avoid Abstract Types in Structs + +**โŒ Type-Unstable:** + +```julia +struct Container + items::Vector{Number} # Abstract type! +end +``` + +**โœ… Type-Stable:** + +```julia +struct Container{T <: Number} + items::Vector{T} # Concrete type parameter +end +``` + +## Common Type Instabilities + +### 1. Untyped Containers + +```julia +# โŒ Type-unstable +results = [] # Vector{Any} + +# โœ… Type-stable +results = Int[] +``` + +### 2. Conditional Return Types + +```julia +# โŒ Type-unstable: Union{Int, Nothing} +function get_value(x::Int) + if x > 0 + return x + else + return nothing + end +end + +# โœ… Type-stable +function get_value(x::Int)::Int + return x > 0 ? x : 0 +end +``` + +### 3. Global Variables + +```julia +# โŒ Type-unstable +global_counter = 0 + +# โœ… Type-stable +const GLOBAL_COUNTER = Ref(0) +``` + +### 4. Type-Unstable Fields + +```julia +# โŒ Type-unstable +mutable struct Cache + data::Any +end + +# โœ… Type-stable +mutable struct Cache{T} + data::T +end +``` + +## Fixing Type Instabilities + +### Strategy 1: Add Type Annotations + +```julia +function process(x::Vector{Float64}) + result = Float64[] + # ... +end +``` + +### Strategy 2: Use Function Barriers + +```julia +# Type-unstable outer function +function outer(data::Dict{Symbol, Any}) + value = data[:key] # Type-unstable + return inner(value) # Function barrier isolates instability +end + +# Type-stable inner function +function inner(value::Int) + return value^2 +end +``` + +### Strategy 3: Parametric Types + +```julia +# Before +struct Container + data::Vector{Any} +end + +# After +struct Container{T} + data::Vector{T} +end +``` + +## When Type Stability Matters + +### Critical Paths (must be type-stable) + +- Inner loops (called millions of times) +- Hot paths in solvers +- Numerical computations +- Real-time systems + +### Less Critical Paths + +- One-time setup code +- User-facing API layers +- Error handling paths +- Debugging utilities + +## Performance Testing + +```julia +@testset "Allocations" begin + ocp = create_test_ocp() + + allocs = @allocated state_dimension(ocp) + @test allocs == 0 + + allocs = @allocated build_model(ocp) + @test allocs < 1000 # bytes +end +``` + +## Quality Checklist + +Before finalizing code, verify: + +- [ ] Critical functions tested with `@inferred` +- [ ] No `Any` types in hot paths +- [ ] Parametric types used where appropriate +- [ ] `@code_warntype` shows no red flags +- [ ] Allocation tests pass for critical operations +- [ ] Benchmarks meet performance targets + +## Key Takeaways + +1. Type stability is crucial for Julia performance +2. Test with `@inferred` for all critical functions +3. Use parametric types and NamedTuple for type-stable structures +4. Avoid `Any` and abstract types in hot paths +5. Use `@code_warntype` to debug instabilities +6. Test allocations for performance-critical code + +## Related Skills + +- `performance` skill โ€” profiling, benchmarking, allocation reduction +- `testing-creation` skill โ€” type stability test patterns +- `architecture` skill โ€” parametric type design