diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0aa323f..63e0ed5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,6 +32,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e '.[dev,jax,web]' + pip install -e '.[dev,jax,torch,web]' - name: Test run: pytest \ No newline at end of file diff --git a/conftest.py b/conftest.py index 432e51e..2b745b8 100644 --- a/conftest.py +++ b/conftest.py @@ -2,14 +2,14 @@ import pytest import numpy -BACKENDS: set[str] = set(('cpu', 'jax', 'cuda')) +BACKENDS: set[str] = set(('cpu', 'jax', 'cupy', 'torch')) AVAILABLE_BACKENDS: set[str] = set(('cpu',)) try: import cupy # pyright: ignore[reportMissingImports] if cupy.cuda.runtime.getDeviceCount() > 0: - AVAILABLE_BACKENDS.add('cuda') -except ImportError: + AVAILABLE_BACKENDS.add('cupy') +except (ImportError, RuntimeError): pass try: @@ -19,6 +19,14 @@ pass +try: + import torch # pyright: ignore[reportMissingImports] # noqa: F401 + torch.asarray([1, 2, 3, 4]).numpy(force=True) # ensures torch is loaded, fixes a strange error with pytest + AVAILABLE_BACKENDS.add('torch') +except ImportError: + pass + + def pytest_addoption(parser: pytest.Parser): parser.addoption("--save-expected", action="store_true", dest='save-expected', default=False, help="Overwrite expected files with the results of tests.") diff --git a/examples/mos2_epie.yaml b/examples/mos2_epie.yaml index 6005257..b7fe806 100644 --- a/examples/mos2_epie.yaml +++ b/examples/mos2_epie.yaml @@ -1,11 +1,11 @@ --- name: "mos2_epie" -backend: cupy +backend: torch # raw data source raw_data: type: empad - path: "sample_data/simulated_mos2/mos2_0.00_dstep1.0.json" + path: "~/Downloads/mos2/1/mos2/mos2_0.00_dstep1.0.json" post_load: - type: poisson @@ -32,7 +32,8 @@ engines: beta_probe: 0.5 group_constraints: [] - iter_constraints: [] + iter_constraints: + - type: remove_phase_ramp update_probe: {after: 5} diff --git a/examples/optuna_study.py b/examples/optuna_study.py index 95d527d..1f74b48 100755 --- a/examples/optuna_study.py +++ b/examples/optuna_study.py @@ -136,7 +136,7 @@ def save_json(self, plan: ReconsPlan, engines: t.Iterable[EngineHook]): with open(self.trial_path / 'plan.json', 'w') as f: json.dump(plan_json, f, indent=4) - def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional[float] = None): + def update_iteration(self, state: ReconsState, i: int, n: int, errors: t.Dict[str, float]): i = state.iter.total_iter if i % MEASURE_EVERY == 0: diff --git a/notebooks/conventions.ipynb b/notebooks/conventions.ipynb index ace72a1..6c745f6 100644 --- a/notebooks/conventions.ipynb +++ b/notebooks/conventions.ipynb @@ -74,6 +74,25 @@ "\n", "Nevertheless, pixels are often drawn as \"little squares\", requiring us to choose a convention of where to place these squares. We place each pixel at the center of the sampling point it represents. e.g. the pixel center is at integer coordinates." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notes for backend-agnostic coding\n", + "\n", + "Backends are libraries which support the Array API: https://data-apis.org/array-api/2024.12/\n", + "\n", + "https://github.com/pytorch/pytorch/issues/58743\n", + "\n", + "- Use `xp.size()` rather than `arr.size` (for Torch)\n", + "- Use `at()` util for in-place modifications (for Jax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { diff --git a/phaser/engines/common/noise_models.py b/phaser/engines/common/noise_models.py index 66cd3be..c366f24 100644 --- a/phaser/engines/common/noise_models.py +++ b/phaser/engines/common/noise_models.py @@ -6,7 +6,7 @@ from phaser.hooks.solver import NoiseModel from phaser.plan import AmplitudeNoisePlan, AnscombeNoisePlan, PoissonNoisePlan -from phaser.utils.num import get_array_module, Float +from phaser.utils.num import get_array_module, Float, to_numpy from phaser.state import ReconsState diff --git a/phaser/engines/common/regularizers.py b/phaser/engines/common/regularizers.py index 15c9fc1..a3dd19c 100644 --- a/phaser/engines/common/regularizers.py +++ b/phaser/engines/common/regularizers.py @@ -1,5 +1,6 @@ from functools import partial import logging +from math import prod import typing as t import numpy @@ -196,6 +197,10 @@ class ObjL1: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'obj_l1' + def init_state(self, sim: ReconsState) -> None: return None @@ -205,7 +210,7 @@ def calc_loss_group( xp = get_array_module(sim.object.data) cost = xp.sum(xp.abs(sim.object.data - 1.0)) - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) return (cost * cost_scale * self.cost, state) @@ -213,6 +218,10 @@ class ObjL2: def __init__(self, args: None, props: CostRegularizerProps): self.cost: Float = props.cost + @staticmethod + def name() -> str: + return 'obj_l2' + def init_state(self, sim: ReconsState) -> None: return None @@ -222,14 +231,19 @@ def calc_loss_group( xp = get_array_module(sim.object.data) cost = xp.sum(abs2(sim.object.data - 1.0)) - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) - return (cost * cost_scale * self.cost, state) + + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) + return (cost * cost_scale * self.cost, state) # type: ignore class ObjPhaseL1: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'obj_phase_l1' + def init_state(self, sim: ReconsState) -> None: return None @@ -239,7 +253,7 @@ def calc_loss_group( xp = get_array_module(sim.object.data) cost = xp.sum(xp.abs(xp.angle(sim.object.data))) - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) return (cost * cost_scale * self.cost, state) @@ -247,6 +261,10 @@ class ObjRecipL1: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'obj_recip_l1' + def init_state(self, sim: ReconsState) -> None: return None @@ -261,7 +279,7 @@ def calc_loss_group( xp.abs(fft2(xp.prod(sim.object.data, axis=0))) ) # scale cost by fraction of the total reconstruction in the group - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) return (cost * cost_scale * self.cost, state) @@ -271,6 +289,10 @@ def __init__(self, args: None, props: TVRegularizerProps): self.cost: float = props.cost self.eps: float = props.eps + @staticmethod + def name() -> str: + return 'obj_tv' + def init_state(self, sim: ReconsState) -> None: return None @@ -289,7 +311,7 @@ def calc_loss_group( #) # scale cost by fraction of the total reconstruction in the group # TODO also scale by # of pixels or similar? - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) return (cost * cost_scale * self.cost, state) @@ -298,6 +320,10 @@ class ObjTikhonov: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'obj_tikh' + def init_state(self, sim: ReconsState) -> None: return None @@ -311,15 +337,19 @@ def calc_loss_group( xp.sum(abs2(xp.diff(sim.object.data, axis=-2))) ) # scale cost by fraction of the total reconstruction in the group - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) - return (cost * cost_scale * self.cost, state) + return (cost * cost_scale * self.cost, state) # type: ignore class LayersTotalVariation: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'layers_tv' + def init_state(self, sim: ReconsState) -> None: return None @@ -333,7 +363,7 @@ def calc_loss_group( cost = xp.sum(xp.abs(xp.diff(sim.object.data, axis=0))) # scale cost by fraction of the total reconstruction in the group - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) return (cost * cost_scale * self.cost, state) @@ -342,6 +372,10 @@ class LayersTikhonov: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'layers_tikh' + def init_state(self, sim: ReconsState) -> None: return None @@ -355,15 +389,19 @@ def calc_loss_group( cost = xp.sum(abs2(xp.diff(sim.object.data, axis=0))) # scale cost by fraction of the total reconstruction in the group - cost_scale = (group.shape[-1] / numpy.prod(sim.scan.shape[:-1])).astype(cost.dtype) + cost_scale = xp.array(group.shape[-1] / prod(sim.scan.shape[:-1]), dtype=cost.dtype) - return (cost * cost_scale * self.cost, state) + return (cost * cost_scale * self.cost, state) # type: ignore class ProbePhaseTikhonov: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'probe_phase_tikh' + def init_state(self, sim: ReconsState) -> None: return None @@ -387,6 +425,10 @@ class ProbeRecipTikhonov: def __init__(self, args: None, props: CostRegularizerProps): self.cost: float = props.cost + @staticmethod + def name() -> str: + return 'probe_recip_tikh' + def init_state(self, sim: ReconsState) -> None: return None @@ -410,6 +452,10 @@ def __init__(self, args: None, props: TVRegularizerProps): self.cost: float = props.cost self.eps: float = props.eps + @staticmethod + def name() -> str: + return 'probe_recip_tv' + def init_state(self, sim: ReconsState) -> None: return None diff --git a/phaser/engines/common/simulation.py b/phaser/engines/common/simulation.py index fb88163..a6e15b6 100644 --- a/phaser/engines/common/simulation.py +++ b/phaser/engines/common/simulation.py @@ -3,14 +3,15 @@ import typing as t import numpy -from numpy.typing import NDArray, DTypeLike +from numpy.typing import NDArray from typing_extensions import Self from phaser.utils.num import ( get_array_module, to_real_dtype, to_complex_dtype, fft2, ifft2, is_jax, to_numpy, block_until_ready, ufunc_outer ) -from phaser.utils.misc import FloatKey, jax_dataclass, create_compact_groupings, create_sparse_groupings, shuffled +from phaser.utils.tree import tree_dataclass +from phaser.utils.misc import FloatKey, create_compact_groupings, create_sparse_groupings, shuffled from phaser.utils.optics import fresnel_propagator, fourier_shift_filter from phaser.state import ReconsState from phaser.hooks.solver import NoiseModel @@ -31,7 +32,8 @@ def __init__( self.compact = compact self.seed = seed self.groups: t.Optional[t.List[NDArray[numpy.int64]]] = None - self.n_groups: int = int(numpy.ceil(numpy.prod(scan.shape[:-1]) / self.grouping)) + self.n_pos = numpy.prod(scan.shape[:-1]) + self.n_groups: int = int(numpy.ceil(self.n_pos / self.grouping)) def _make(self, scan: NDArray[numpy.floating], i: int = 0) -> t.List[NDArray[numpy.int64]]: if self.compact: @@ -83,7 +85,7 @@ def stream_patterns( continue -@jax_dataclass(init=False, static_fields=('xp', 'dtype', 'noise_model', 'group_constraints', 'iter_constraints'), drop_fields=('ky', 'kx')) +@tree_dataclass(init=False, static_fields=('xp', 'dtype', 'noise_model', 'group_constraints', 'iter_constraints'), drop_fields=('ky', 'kx')) class SimulationState: state: ReconsState @@ -99,7 +101,7 @@ class SimulationState: iter_constraint_states: t.Tuple[t.Any, ...] xp: t.Any - dtype: DTypeLike + dtype: t.Type[numpy.floating] start_iter: int def __init__( @@ -109,7 +111,7 @@ def __init__( group_constraints: t.Tuple[GroupConstraint[t.Any], ...], iter_constraints: t.Tuple[IterConstraint[t.Any], ...], xp: t.Any, - dtype: DTypeLike, + dtype: t.Type[numpy.floating], noise_model_state: t.Optional[t.Any] = None, group_constraint_states: t.Optional[t.Tuple[t.Any, ...]] = None, iter_constraint_states: t.Optional[t.Tuple[t.Any, ...]] = None, diff --git a/phaser/engines/conventional/run.py b/phaser/engines/conventional/run.py index 578fcdf..a31bd9a 100644 --- a/phaser/engines/conventional/run.py +++ b/phaser/engines/conventional/run.py @@ -1,13 +1,11 @@ import logging -import numpy - from phaser.utils.misc import mask_fraction_of_groups -from phaser.utils.num import cast_array_module, to_numpy, to_complex_dtype +from phaser.utils.num import assert_dtype, cast_array_module, to_numpy, to_complex_dtype from phaser.observer import Observer from phaser.hooks import EngineArgs from phaser.plan import ConventionalEnginePlan -from phaser.state import ReconsState +from phaser.state import ReconsState, ProgressState from phaser.types import process_flag from ..common.simulation import SimulationState, make_propagators, GroupManager @@ -17,6 +15,7 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: xp = cast_array_module(args['xp']) dtype = args['dtype'] + cdtype = to_complex_dtype(dtype) observer: Observer = args.get('observer', Observer()) seed = args['seed'] @@ -37,12 +36,12 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: xp=xp, dtype=dtype ) patterns = args['data'].patterns - pattern_mask = xp.array(args['data'].pattern_mask) + pattern_mask = xp.asarray(args['data'].pattern_mask) - assert patterns.dtype == sim.dtype - assert pattern_mask.dtype == sim.dtype - assert sim.state.object.data.dtype == to_complex_dtype(sim.dtype) - assert sim.state.probe.data.dtype == to_complex_dtype(sim.dtype) + assert_dtype(patterns, dtype) + assert_dtype(pattern_mask, dtype) + assert_dtype(sim.state.object.data, cdtype) + assert_dtype(sim.state.probe.data, cdtype) solver = props.solver(props) sim = solver.init(sim) @@ -53,6 +52,14 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: position_solver = None if props.position_solver is None else props.position_solver(None) position_solver_state = None if position_solver is None else position_solver.init_state(sim.state) + # populate missing keys in progress dictionary + for k in ('detector_loss', 'total_loss'): + if k not in sim.state.progress: + sim.state.progress[k] = ProgressState() + + # save progress, it will get clobbered by JIT kernels + progress = sim.state.progress + observer.init_engine( sim.state, recons_name=args['recons_name'], plan=props, noise_model=noise_model.name(), @@ -68,6 +75,7 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: propagators=propagators ) + sim.state.progress = progress observer.start_engine(sim.state) for i in range(1, props.niter+1): @@ -87,8 +95,8 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: calc_error_mask=calc_error_mask, observer=observer, ) - assert sim.state.object.data.dtype == to_complex_dtype(sim.dtype) - assert sim.state.probe.data.dtype == to_complex_dtype(sim.dtype) + assert_dtype(sim.state.object.data, cdtype) + assert_dtype(sim.state.probe.data, cdtype) sim = sim.apply_iter_constraints() @@ -104,7 +112,7 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: update_mag = xp.linalg.norm(pos_update, axis=-1, keepdims=True) logger.info(f"Position update: mean {xp.mean(update_mag)}") sim.state.scan += pos_update - assert sim.state.scan.dtype == sim.dtype + assert_dtype(sim.state.scan, dtype) # check positions are at least overlapping object sim.state.object.sampling.check_scan(sim.state.scan, sim.state.probe.sampling.extent / 2.) @@ -113,11 +121,12 @@ def run_engine(args: EngineArgs, props: ConventionalEnginePlan) -> ReconsState: if group_errors is not None and len(group_errors): error = float(to_numpy(xp.nanmean(xp.concatenate(group_errors)))) - # TODO don't do this - sim.state.progress.iters = numpy.concatenate([sim.state.progress.iters, [i + start_i]]) - sim.state.progress.detector_errors = numpy.concatenate([sim.state.progress.detector_errors, [error]]) + for k in ('detector_loss', 'total_loss'): + progress[k].iters.append(i + start_i) + progress[k].values.append(error) - observer.update_iteration(sim.state, i, props.niter, error) + sim.state.progress = progress + observer.update_iteration(sim.state, i, props.niter, {'total_loss': error}) observer.finish_engine(sim.state) return sim.state \ No newline at end of file diff --git a/phaser/engines/conventional/solvers.py b/phaser/engines/conventional/solvers.py index 7d6cdc5..d689d95 100644 --- a/phaser/engines/conventional/solvers.py +++ b/phaser/engines/conventional/solvers.py @@ -107,8 +107,8 @@ def run_iteration( gamma=gamma, ) check_finite(sim.state.object.data, sim.state.probe.data, context=f"object or probe, group {group_i}") - assert sim.state.object.data.dtype == to_complex_dtype(sim.dtype) - assert sim.state.probe.data.dtype == to_complex_dtype(sim.dtype) + #assert sim.state.object.data.dtype == to_complex_dtype(sim.dtype) + #assert sim.state.probe.data.dtype == to_complex_dtype(sim.dtype) sim = sim.apply_group_constraints(group) @@ -365,8 +365,8 @@ def run_iteration( update_probe=update_probe, ) check_finite(sim.state.object.data, sim.state.probe.data, context=f"object or probe, group {group_i}") - assert sim.state.object.data.dtype == to_complex_dtype(sim.dtype) - assert sim.state.probe.data.dtype == to_complex_dtype(sim.dtype) + #assert sim.state.object.data.dtype == to_complex_dtype(sim.dtype) + #assert sim.state.probe.data.dtype == to_complex_dtype(sim.dtype) sim = sim.apply_group_constraints(group) diff --git a/phaser/engines/gradient/run.py b/phaser/engines/gradient/run.py index 84a4cce..ecb7452 100644 --- a/phaser/engines/gradient/run.py +++ b/phaser/engines/gradient/run.py @@ -7,14 +7,14 @@ from typing_extensions import Self from phaser.hooks.solver import NoiseModel -from phaser.utils.misc import jax_dataclass from phaser.utils.num import ( - get_array_module, cast_array_module, jit, - fft2, ifft2, abs2, check_finite, at, Float, to_real_dtype + assert_dtype, get_array_module, cast_array_module, jit, + fft2, ifft2, abs2, check_finite, at, Float, to_complex_dtype, to_real_dtype ) +import phaser.utils.tree as tree from phaser.utils.optics import fourier_shift_filter from phaser.observer import Observer -from phaser.state import ReconsState +from phaser.state import ProgressState, ReconsState from phaser.hooks import EngineArgs from phaser.hooks.solver import GradientSolver from phaser.hooks.regularization import CostRegularizer, GroupConstraint @@ -73,13 +73,14 @@ def process_solvers( ('tilt',): 'tilt' } -def extract_vars(state: ReconsState, vars: t.AbstractSet[ReconsVar], group: t.Optional[NDArray[numpy.integer]] = None) -> t.Tuple[t.Dict[ReconsVar, t.Any], ReconsState]: - import jax.tree_util +def _normalize_path(path: t.Tuple[tree.GetAttrKey, ...]) -> t.Tuple[str, ...]: + return tuple(p.name for p in path) +def extract_vars(state: ReconsState, vars: t.AbstractSet[ReconsVar], group: t.Optional[NDArray[numpy.integer]] = None) -> t.Tuple[t.Dict[ReconsVar, t.Any], ReconsState]: d = {} - def f(path: t.Tuple[str, ...], val: t.Any): - if (var := _PATH_MAP.get(path)) and var in vars: + def f(path: t.Tuple[tree.GetAttrKey, ...], val: t.Any): + if (var := _PATH_MAP.get(_normalize_path(path))) and var in vars: if var in _PER_ITER_VARS and group is not None: d[var] = val[tuple(group)] else: @@ -87,21 +88,19 @@ def f(path: t.Tuple[str, ...], val: t.Any): return None return val - state = jax.tree_util.tree_map_with_path(f, state, is_leaf=lambda x: x is None) + state = tree.map_with_path(f, state, is_leaf=lambda x: x is None) return (d, state) def insert_vars(vars: t.Dict[ReconsVar, t.Any], state: ReconsState, group: t.Optional[NDArray[numpy.integer]] = None) -> ReconsState: - import jax.tree_util - - def f(path: t.Tuple[str, ...], val: t.Any): - if (var := _PATH_MAP.get(path)): + def f(path: t.Tuple[tree.GetAttrKey, ...], val: t.Any): + if (var := _PATH_MAP.get(_normalize_path(path))): if var in vars: return vars[var] if var in _PER_ITER_VARS and val is not None and group is not None: return val[tuple(group)] return val - return jax.tree_util.tree_map_with_path(f, state, is_leaf=lambda x: x is None) + return tree.map_with_path(f, state, is_leaf=lambda x: x is None) def apply_update(state: ReconsState, update: t.Dict[ReconsVar, numpy.ndarray]) -> ReconsState: @@ -128,7 +127,7 @@ def filter_vars(d: t.Dict[ReconsVar, t.Any], vars: t.AbstractSet[ReconsVar]) -> return {k: v for (k, v) in d.items() if k in vars} -@jax_dataclass +@tree.tree_dataclass class SolverStates: noise_model_state: t.Any group_solver_states: t.List[t.Any] @@ -154,18 +153,17 @@ def init_state( def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: - import jax - import jax.numpy - from optax.tree_utils import tree_zeros_like - jax.config.update('jax_traceback_filtering', 'off') - - xp = cast_array_module(jax.numpy) - dtype = t.cast(t.Type[numpy.floating], args['dtype']) + #jax.config.update('jax_traceback_filtering', 'off') + xp = cast_array_module(args['xp']) + dtype = args['dtype'] + cdtype = to_complex_dtype(dtype) observer: Observer = args.get('observer', Observer()) state = args['state'] seed = args['seed'] patterns = args['data'].patterns - pattern_mask = args['data'].pattern_mask + pattern_mask = xp.array(args['data'].pattern_mask) + assert_dtype(patterns, dtype) + assert_dtype(pattern_mask, dtype) noise_model = props.noise_model(None) @@ -204,7 +202,7 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: rescale_factor = xp.mean(rescale_factors) logger.info("Pre-calculated intensities") - logger.info(f"Rescaling initial probe intensity by {rescale_factor:.2e}") + logger.info(f"Rescaling initial probe intensity by {float(rescale_factor):.2e}") state.probe.data *= xp.sqrt(rescale_factor) probe_int = xp.sum(abs2(state.probe.data)) @@ -214,7 +212,15 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: iter_solver_states = [solver.init_state(state) for solver in iter_solvers] iter_constraint_states = [reg.init_state(state) for reg in iter_constraints] - #with jax.profiler.trace("/tmp/jax-trace", create_perfetto_link=True): + loss_keys = ('detector_loss', 'total_loss', *(reg.name() for reg in regularizers)) + # populate missing keys in progress dictionary + for k in loss_keys: + if k not in state.progress: + state.progress[k] = ProgressState() + + # progress gets clobbered by the jits, so we keep track of it manually + progress = state.progress + for i in range(1, props.niter+1): state.iter.engine_iter = i state.iter.total_iter = start_i + i @@ -224,10 +230,13 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: set(k for (k, flag) in flags.items() if flag({'state': state, 'niter': props.niter})) ) # gradients for per-iteration solvers - iter_grads = tree_zeros_like(extract_vars(state, iter_vars & _PER_ITER_VARS)[0]) + iter_grads = tree.zeros_like(extract_vars(state, iter_vars & _PER_ITER_VARS)[0]) # whether to shuffle groups this iteration iter_shuffle_groups = shuffle_groups({'state': state, 'niter': props.niter}) + # accumulated losses across groups + losses: t.Dict[str, float] = {k: 0.0 for k in loss_keys} + # update schedules for this iteration # this needs to be done outside the JIT context, which makes this kinda hacky solver_states.group_solver_states = [ @@ -238,11 +247,10 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: solver.update_for_iter(state, solver_state, props.niter) for (solver, solver_state) in zip(iter_solvers, iter_solver_states) ] - losses = [] for (group_i, (group, group_patterns)) in enumerate(stream_patterns(groups.iter(state.scan, i, iter_shuffle_groups), patterns, xp=xp, buf_n=props.buffer_n_groups)): - (state, loss, iter_grads, solver_states) = run_group( + (state, group_losses, iter_grads, solver_states) = run_group( state, group=group, vars=iter_vars, noise_model=noise_model, group_solvers=group_solvers, @@ -257,11 +265,16 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: xp=xp, dtype=dtype ) - losses.append(loss) + losses = tree.map(xp.add, losses, group_losses) + check_finite(state.object.data, state.probe.data, context=f"object or probe, group {group_i}") observer.update_group(state, props.send_every_group) - loss = float(numpy.mean(losses)) + # report losses normalized by # of probe positions + losses = tree.map(lambda v: float(v / groups.n_pos), losses) + for (k, v) in losses.items(): + progress[k].iters.append(int(i + start_i)) + progress[k].values.append(float(v)) # update per-iteration solvers for (sol_i, solver) in enumerate(iter_solvers): @@ -269,7 +282,7 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: if len(solver_grads) == 0: continue (update, iter_solver_states[sol_i]) = solver.update( - state, iter_solver_states[sol_i], filter_vars(iter_grads, solver.params), loss + state, iter_solver_states[sol_i], filter_vars(iter_grads, solver.params), losses['total_loss'] ) state = apply_update(state, update) @@ -278,13 +291,16 @@ def run_engine(args: EngineArgs, props: GradientEnginePlan) -> ReconsState: state, iter_constraint_states[reg_i] ) + assert_dtype(state.object.data, cdtype) + assert_dtype(state.probe.data, cdtype) + if 'positions' in iter_vars: # check positions are at least overlapping object state.object.sampling.check_scan(state.scan, state.probe.sampling.extent / 2.) + assert_dtype(state.scan, dtype) - state.progress.iters = numpy.concatenate([state.progress.iters, [i + start_i]]) - state.progress.detector_errors = numpy.concatenate([state.progress.detector_errors, [loss]]) - observer.update_iteration(state, i, props.niter, loss) + state.progress = progress + observer.update_iteration(state, i, props.niter, losses) observer.finish_engine(state) return state @@ -311,33 +327,33 @@ def run_group( probe_int: t.Union[float, numpy.floating], xp: t.Any, dtype: t.Type[numpy.floating], -) -> t.Tuple[ReconsState, float, t.Dict[ReconsVar, t.Any], SolverStates]: - import jax +) -> t.Tuple[ReconsState, t.Dict[str, Float], t.Dict[ReconsVar, t.Any], SolverStates]: xp = cast_array_module(xp) - ((loss, solver_states), grad) = jax.value_and_grad(run_model, has_aux=True)( + (grad, (solver_states, losses)) = tree.grad(run_model, has_aux=True, xp=xp, sign=-1)( *extract_vars(state, vars, group), group=group, props=props, group_patterns=group_patterns, pattern_mask=pattern_mask, noise_model=noise_model, regularizers=regularizers, solver_states=solver_states, xp=xp, dtype=dtype ) - # steepest descent direction - grad = jax.tree.map(lambda v: -v.conj(), grad, is_leaf=lambda x: x is None) for k in grad.keys(): - if k == 'probe': - grad[k] /= group.shape[-1] - else: - grad[k] /= probe_int * group.shape[-1] + # scale gradients appropriately + # per-pattern variables are normalized by the grouping `group.shape[-1]` + # Additionally, all gradients except the probe should be normalized by probe intensity + grad[k] /= xp.array( + (1.0 if k in _PER_ITER_VARS else group.shape[-1]) * (1.0 if k == 'probe' else probe_int), + dtype=dtype + ) # update iter grads at group - iter_grads = jax.tree.map(lambda v1, v2: at(v1, tuple(group)).set(v2), iter_grads, filter_vars(grad, vars & _PER_ITER_VARS)) + iter_grads = tree.map(lambda v1, v2: at(v1, tuple(group)).set(v2), iter_grads, filter_vars(grad, vars & _PER_ITER_VARS)) for (sol_i, solver) in enumerate(group_solvers): solver_grads = filter_vars(grad, solver.params) if len(solver_grads) == 0: continue (update, solver_states.group_solver_states[sol_i]) = solver.update( - state, solver_states.group_solver_states[sol_i], solver_grads, loss + state, solver_states.group_solver_states[sol_i], solver_grads, losses['total_loss'] ) state = apply_update(state, update) @@ -346,7 +362,7 @@ def run_group( group, state, solver_states.group_constraint_states[reg_i] ) - return (state, loss, iter_grads, solver_states) + return (state, losses, iter_grads, solver_states) @partial( @@ -366,7 +382,7 @@ def run_model( solver_states: SolverStates, xp: t.Any, dtype: t.Type[numpy.floating], -) -> t.Tuple[Float, SolverStates]: +) -> t.Tuple[Float, t.Tuple[SolverStates, t.Dict[str, Float]]]: # apply vars to simulation sim = insert_vars(vars, sim, group) group_scan = sim.scan @@ -396,13 +412,18 @@ def sim_slice(slice_i: int, prop: t.Optional[NDArray[numpy.complexfloating]], ps model_wave, model_intensity, group_patterns, pattern_mask, solver_states.noise_model_state ) + losses: t.Dict[str, Float] = {'detector_loss': loss} + for (reg_i, reg) in enumerate(regularizers): (reg_loss, solver_states.regularizer_states[reg_i]) = reg.calc_loss_group( group, sim, solver_states.regularizer_states[reg_i] ) + losses[reg.name()] = reg_loss loss += reg_loss - return (loss, solver_states) + losses['total_loss'] = loss + + return (loss, (solver_states, losses)) # TODO: DRY diff --git a/phaser/engines/gradient/solvers.py b/phaser/engines/gradient/solvers.py index f0d3425..4ddccdd 100644 --- a/phaser/engines/gradient/solvers.py +++ b/phaser/engines/gradient/solvers.py @@ -1,91 +1,104 @@ -import logging +""" +Gradient-descent solvers + +Much of this is adapted from [Optax](https://github.com/google-deepmind/optax), +but modified to use our generic array and pytree utilities. + +Optax is released under the Apache license: + +> Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +> +> Licensed under the Apache License, Version 2.0 (the "License"); +> you may not use this file except in compliance with the License. +> You may obtain a copy of the License at +> +> http://www.apache.org/licenses/LICENSE-2.0 +> +> Unless required by applicable law or agreed to in writing, software +> distributed under the License is distributed on an "AS IS" BASIS, +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +> See the License for the specific language governing permissions and +> limitations under the License. +""" + import typing as t import numpy -from numpy.typing import NDArray, ArrayLike +from numpy.typing import NDArray -from phaser.utils.num import as_array, abs2 +from phaser.utils.num import get_array_module +import phaser.utils.tree as tree from phaser.hooks.solver import GradientSolver, GradientSolverArgs -from phaser.hooks.schedule import FlagArgs, ScheduleLike +from phaser.hooks.schedule import ScheduleLike, Schedule from phaser.types import ReconsVar, process_schedule -from phaser.plan import GradientEnginePlan, AdamSolverPlan, PolyakSGDSolverPlan, SGDSolverPlan +from phaser.plan import AdamSolverPlan, PolyakSGDSolverPlan, SGDSolverPlan from phaser.state import ReconsState -from .run import extract_vars, apply_update +from .run import extract_vars -import optax -from optax import GradientTransformation, GradientTransformationExtraArgs -from optax.schedules import StatefulSchedule +OptState: t.TypeAlias = tree.Tree +Params: t.TypeAlias = tree.Tree +Updates: t.TypeAlias = Params -class OptaxScheduleWrapper(StatefulSchedule): - def __init__(self, schedule: ScheduleLike): - self.inner = process_schedule(schedule) +class TransformInitFn(t.Protocol): + def __call__(self, params: Params) -> OptState: + ... - def init(self) -> t.Optional[float]: - return None - def update_for_iter(self, sim: ReconsState, state: t.Optional[float], niter: int) -> float: - return self.inner({'state': sim, 'niter': niter}) +class TransformUpdateFn(t.Protocol): + def __call__( + self, updates: Updates, state: OptState, params: t.Optional[Params] = None, + **extra_args: t.Any, + ) -> t.Tuple[Updates, OptState]: + ... - # mock update from inside jax - def update( - self, state: t.Optional[float], - **extra_args, - ) -> t.Optional[float]: - return state - def __call__( - self, state: t.Optional[float], - **extra_args, - ) -> float: - assert state is not None - return state +class GradientTransformation(t.NamedTuple): + init: TransformInitFn + update: TransformUpdateFn -OptaxSolverState: t.TypeAlias = t.Tuple[t.Any, t.Dict[str, t.Optional[float]]] +ScheduledSolverState: t.TypeAlias = t.Tuple[t.Any, t.Dict[str, t.Optional[float]]] -class OptaxSolver(GradientSolver[OptaxSolverState]): +class ScheduledSolver(GradientSolver[ScheduledSolverState]): def __init__(self, name: str, factory: t.Callable[..., GradientTransformation], hyperparams: t.Mapping[str, ScheduleLike], params: t.Iterable[ReconsVar]): self.factory: t.Callable[..., GradientTransformation] = factory #self.inner: GradientTransformationExtraArgs = optax.with_extra_args_support(solver) - self.hyperparams: t.Dict[str, OptaxScheduleWrapper] = {k: OptaxScheduleWrapper(v) for (k, v) in hyperparams.items()} + self.hyperparams: t.Dict[str, Schedule] = {k: process_schedule(v) for (k, v) in hyperparams.items()} self.params: t.FrozenSet[ReconsVar] = frozenset(params) self.name: str = name # or self.inner.__class__.__name__ - def init_state(self, sim: ReconsState) -> OptaxSolverState: + def init_state(self, sim: ReconsState) -> ScheduledSolverState: return ( None, - {k: v.init() for (k, v) in self.hyperparams.items()}, + {k: None for (k, v) in self.hyperparams.items()}, ) - def _resolve(self, hparams: t.Mapping[str, t.Optional[float]]) -> GradientTransformationExtraArgs: - return optax.with_extra_args_support( - self.factory(**{k: v(hparams[k]) for (k, v) in self.hyperparams.items()}) - ) + def _resolve(self, hparams: t.Mapping[str, t.Optional[float]]) -> GradientTransformation: + return self.factory(**{k: hparams[k] for k in self.hyperparams.keys()}) - def update_for_iter(self, sim: ReconsState, state: OptaxSolverState, niter: int) -> OptaxSolverState: - hparams_state: t.Dict[str, t.Optional[float]] = {k: v.update_for_iter(sim, state[1][k], niter) for (k, v) in self.hyperparams.items()} + def update_for_iter(self, sim: ReconsState, state: ScheduledSolverState, niter: int) -> ScheduledSolverState: + hparams_state: t.Dict[str, t.Optional[float]] = {k: v({'state': sim, 'niter': niter}) for (k, v) in self.hyperparams.items()} return ( self._resolve(hparams_state).init(params=extract_vars(sim, self.params)[0]) if state[0] is None else state[0], hparams_state ) def update( - self, sim: 'ReconsState', state: OptaxSolverState, grad: t.Dict[ReconsVar, numpy.ndarray], loss: float, - ) -> t.Tuple[t.Dict[ReconsVar, numpy.ndarray], OptaxSolverState]: + self, sim: 'ReconsState', state: ScheduledSolverState, grad: t.Dict[ReconsVar, numpy.ndarray], loss: float, + ) -> t.Tuple[t.Dict[ReconsVar, numpy.ndarray], ScheduledSolverState]: (inner_state, hparams_state) = state - hparams_state = {k: v.update(hparams_state[k]) for (k, v) in self.hyperparams.items()} (updates, inner_state) = self._resolve(hparams_state).update( grad, inner_state, params=extract_vars(sim, self.params)[0], value=loss, loss=loss ) return (t.cast(t.Dict[ReconsVar, t.Any], updates), (inner_state, hparams_state)) -class SGDSolver(OptaxSolver): +class SGDSolver(ScheduledSolver): def __init__(self, args: GradientSolverArgs, props: SGDSolverPlan): hparams = { 'learning_rate': props.learning_rate @@ -94,33 +107,33 @@ def __init__(self, args: GradientSolverArgs, props: SGDSolverPlan): if props.momentum is not None: hparams['momentum'] = props.momentum def factory(**kwargs: t.Any) -> GradientTransformation: - return optax.chain( - optax.trace(kwargs['momentum'], props.nesterov), - optax.scale_by_learning_rate(kwargs['learning_rate'], flip_sign=False), + return chain( + trace(kwargs['momentum'], props.nesterov), + scale_by_learning_rate(kwargs['learning_rate']), ) else: def factory(**kwargs: t.Any) -> GradientTransformation: - return optax.scale_by_learning_rate(kwargs['learning_rate'], flip_sign=False) + return scale_by_learning_rate(kwargs['learning_rate']) super().__init__('sgd', factory, hparams, args['params']) -class AdamSolver(OptaxSolver): +class AdamSolver(ScheduledSolver): def __init__(self, args: GradientSolverArgs, props: AdamSolverPlan): hparams = { 'learning_rate': props.learning_rate } def factory(**kwargs) -> GradientTransformation: - return optax.chain( - optax.scale_by_adam(props.b1, props.b2, props.eps, props.eps_root, nesterov=props.nesterov), - optax.scale_by_learning_rate(learning_rate=kwargs['learning_rate'], flip_sign=False), + return chain( + scale_by_adam(props.b1, props.b2, props.eps, props.eps_root, nesterov=props.nesterov), + scale_by_learning_rate(learning_rate=kwargs['learning_rate']), ) super().__init__('adam', factory, hparams, args['params']) -class PolyakSGDSolver(OptaxSolver): +class PolyakSGDSolver(ScheduledSolver): def __init__(self, args: GradientSolverArgs, props: PolyakSGDSolverPlan): hparams = { 'max_learning_rate': props.max_learning_rate, @@ -128,12 +141,157 @@ def __init__(self, args: GradientSolverArgs, props: PolyakSGDSolverPlan): } def factory(**kwargs) -> GradientTransformation: - return optax.chain( - optax.scale_by_learning_rate(kwargs['scaling'], flip_sign=False), - optax.scale_by_polyak( + return chain( + scale_by_learning_rate(kwargs['scaling']), + scale_by_polyak( max_learning_rate=kwargs['max_learning_rate'], f_min=props.f_min, eps=props.eps, #variant='sps', ) ) - super().__init__('polyak_sgd', factory, hparams, args['params']) \ No newline at end of file + super().__init__('polyak_sgd', factory, hparams, args['params']) + + +def chain( + *args: GradientTransformation +) -> GradientTransformation: + init_fns = tuple(arg.init for arg in args) + update_fns = tuple(arg.update for arg in args) + + def init_fn(params: Params): + return tuple(fn(params) for fn in init_fns) + + def update_fn(updates, state, params=None, **extra_args): + new_state = [] + for s, fn in zip(state, update_fns): + updates, new_s = fn(updates, s, params, **extra_args) + new_state.append(new_s) + return updates, tuple(new_state) + + return GradientTransformation(init_fn, update_fn) + + +def trace( + decay: float, + nesterov: bool = False, + accumulator_dtype: t.Optional[t.Any] = None, +) -> GradientTransformation: + + def init_fn(params): + return tree.zeros_like(params, dtype=accumulator_dtype) + + def update_fn(updates: Updates, state: Updates, params=None, **extra_args: t.Any): + del params + f = lambda g, t: g + decay * t # noqa: E731 + new_trace = tree.map( + lambda g, t: None if g is None else f(g, t), + updates, + state, + is_leaf=lambda g: g is None, + ) + updates = tree.map(f, updates, new_trace) if nesterov else new_trace + new_trace = tree.cast(new_trace, accumulator_dtype) + return updates, new_trace + + return GradientTransformation(init_fn, update_fn) + + +def scale_by_learning_rate( + learning_rate: float, *, + flip_sign: bool = False, +) -> GradientTransformation: + if flip_sign: + learning_rate *= -1 + + def update_fn(updates: Updates, state: None, params=None, **extra_args: t.Any): + del params + updates = tree.map(lambda g: learning_rate * g, updates) + return updates, state + + return GradientTransformation(lambda params: None, update_fn) + + +class ScaleByAdamState(t.NamedTuple): + n: NDArray[numpy.int32] # shape () + mu: Updates + nu: Updates + + +def scale_by_adam( + b1: float = 0.9, + b2: float = 0.999, + eps: float = 1e-8, + eps_root: float = 0.0, + mu_dtype: t.Optional[t.Any] = None, + *, + nesterov: bool = False, +) -> GradientTransformation: + def init_fn(params: Params) -> ScaleByAdamState: + xp = get_array_module(params) + mu = tree.zeros_like(params, dtype=mu_dtype) # First moment + nu = tree.zeros_like(params) # Second moment + return ScaleByAdamState(n=xp.zeros((), dtype=xp.int32), mu=mu, nu=nu) + + def update_fn( + updates: Updates, state: ScaleByAdamState, params: t.Any = None, **kwargs: t.Any + ) -> t.Tuple[Updates, ScaleByAdamState]: + xp = get_array_module(updates) + del params + mu = tree.update_moment(updates, state.mu, b1, 1) + nu = tree.update_moment_per_elem_norm(updates, state.nu, b2, 2) + n_inc = safe_increment(state.n) + + if nesterov: + mu_hat = tree.map( + lambda m, g: b1 * m + (1 - b1) * g, + tree.bias_correction(mu, b1, safe_increment(n_inc)), + tree.bias_correction(updates, b1, n_inc), + ) + else: + mu_hat = tree.bias_correction(mu, b1, n_inc) + + nu_hat = tree.bias_correction(nu, b2, n_inc) + updates = tree.map( + lambda m, v: None if m is None else m / (xp.sqrt(v + eps_root) + eps), + mu_hat, + nu_hat, + is_leaf=lambda x: x is None, + ) + mu = tree.cast(mu, mu_dtype) + return updates, ScaleByAdamState(n=n_inc, mu=mu, nu=nu) + + return GradientTransformation(init_fn, update_fn) + + +def scale_by_polyak( + f_min: float = 0.0, + max_learning_rate: float = 1.0, + eps: float = 0.0 +) -> GradientTransformation: + def update_fn( + updates: Updates, state: None, params: t.Any = None, *, value: float, **kwargs: t.Any + ): + del params + del kwargs + xp = get_array_module(updates) + grad_sq_norm = tree.squared_norm(updates) + gap = xp.array(value - f_min).astype(grad_sq_norm.dtype) + step = xp.where( + grad_sq_norm + eps <= xp.finfo(float).eps, + xp.array(0.0), + xp.minimum(gap / (grad_sq_norm + eps), max_learning_rate), + ) + updates = tree.scale(step, updates) + return updates, state + + return GradientTransformation(lambda params: None, update_fn) + + +def safe_increment(n: NDArray[numpy.int32]) -> NDArray[numpy.int32]: + xp = get_array_module(n) + + max_value = xp.iinfo(n.dtype).max + max_value = xp.array(max_value, dtype=n.dtype) + return xp.where( + n < max_value, n + xp.ones_like(n), max_value + ) \ No newline at end of file diff --git a/phaser/execute.py b/phaser/execute.py index 597d244..dfcbb06 100644 --- a/phaser/execute.py +++ b/phaser/execute.py @@ -2,18 +2,19 @@ import itertools import logging import math +import sys import typing as t import numpy import pane from phaser.types import EarlyTermination -from phaser.utils.num import cast_array_module, get_array_module, get_backend_module, xp_is_jax, Sampling, to_complex_dtype +from phaser.utils.num import Device, cast_array_module, get_array_module, get_backend_devices, get_backend_module, set_default_device, to_device, xp_is_jax, Sampling, to_complex_dtype, xp_is_torch from phaser.utils.object import ObjectSampling from phaser.utils.misc import unwrap from .hooks import EngineHook, Hook, ObjectHook, RawData from .plan import GradientEnginePlan, ReconsPlan, EnginePlan, ScanHook, ProbeHook, TiltHook -from .state import Patterns, ReconsState, PartialReconsState, IterState, ProgressState, PreparedRecons +from .state import Patterns, ReconsState, PartialReconsState, IterState, PreparedRecons from .observer import Observer, LoggingObserver, PatienceObserver, SaveObserver, ObserverSet @@ -42,7 +43,8 @@ def execute_plan( recons.observer.finish_recons(recons.state) logging.info("Reconstruction finished!") finally: - recons.observer.close() + # pass any excpetion info to observers + recons.observer.close(sys.exc_info()[1]) def execute_engine( @@ -50,7 +52,7 @@ def execute_engine( engine: EngineHook, ) -> PreparedRecons: xp = get_array_module(recons.state.object.data, recons.state.probe.data) - dtype = recons.patterns.patterns.dtype + dtype = recons.patterns.patterns.dtype.type plan = t.cast(EnginePlan, engine.props) engine_i = recons.state.iter.engine_num @@ -178,15 +180,15 @@ def load_raw_data( raw_data['scan_hook'] = pane.into_data(merge( # type: ignore pane.from_data(t.cast(dict, raw_data.get('scan_hook', None)), ScanHook) if raw_data.get('scan_hook', None) is not None else None, - _MISSING if plan.init.scan in (None, {}) else plan.init.scan + None if plan.init.scan in (None, {}) else plan.init.scan )) raw_data['tilt_hook'] = pane.into_data(merge( # type: ignore pane.from_data(t.cast(dict, raw_data.get('tilt_hook', None)), TiltHook) if raw_data.get('tilt_hook', None) is not None else None, - _MISSING if plan.init.tilt in (None, {}) else plan.init.tilt + None if plan.init.tilt in (None, {}) else plan.init.tilt )) raw_data['probe_hook'] = pane.into_data(merge( # type: ignore pane.from_data(t.cast(dict, raw_data.get('probe_hook', None)), ProbeHook) if raw_data.get('probe_hook', None) is not None else None, - _MISSING if plan.init.probe in (None, {}) else plan.init.probe + None if plan.init.probe in (None, {}) else plan.init.probe )) #print(f"scan_hook: {raw_data['scan_hook']}") #print(f"probe_hook: {raw_data['probe_hook']}") @@ -223,16 +225,39 @@ def load_raw_data( def initialize_reconstruction( - plan: ReconsPlan, *, xp: t.Any = None, seed: t.Any = None, - name: t.Optional[str] = None, + plan: ReconsPlan, *, xp: t.Any = None, device: t.Optional[Device] = None, + seed: t.Any = None, name: t.Optional[str] = None, init_state: t.Union[ReconsState, PartialReconsState, None] = None, observers: t.Union[Observer, t.Iterable[Observer], None] = None, override_observers: t.Union[Observer, t.Iterable[Observer], None] = None, ) -> PreparedRecons: - xp = cast_array_module(get_backend_module(plan.backend) if xp is None else xp) + logging.basicConfig(level=logging.INFO) + + with open("post_init_plan.json", "w") as f: + pane.write_json(plan, f, indent=4) + + if xp is not None: + xp = cast_array_module(xp) + # TODO: nicer output here + logging.info(f"Using manually-specified backend {xp}") + devices = get_backend_devices(xp) + logging.info(f"Available devices: {list(devices)}") + manual = device is not None + device = to_device(device, xp) if device is not None else devices[0] + logging.info(f"Using {'manually-specified ' if manual else ''}device {device}") + else: + xp = get_backend_module(plan.backend) + logging.info(f"Using {'plan-specified' if plan.backend is not None else 'default'} backend {xp}") + devices = get_backend_devices(xp) + logging.info(f"Available devices: {list(devices)}") + + device = to_device(plan.device, xp) if plan.device is not None else devices[0] + logging.info(f"Using {'plan-specified ' if plan.device is not None else ''}device {device}") + + set_default_device(device, xp) + observer = _normalize_observers(observers, override_observers) - logging.basicConfig(level=logging.INFO) logging.info("Executing plan...") observer.init_recons(plan) @@ -313,6 +338,7 @@ def initialize_reconstruction( obj.data = obj.data.astype(cdtype) else: logging.info("Initializing object...") + obj = (plan.init.object or pane.from_data('random', ObjectHook))({ 'sampling': obj_sampling, 'slices': plan.slices, 'wavelength': wavelength, 'dtype': dtype, 'seed': seed, 'xp': xp @@ -327,9 +353,9 @@ def initialize_reconstruction( object=obj, scan=scan, tilt=tilt, - progress=ProgressState(iters=numpy.array([]), detector_errors=numpy.array([])), wavelength=wavelength ) + state = state.to_xp(xp) # TODO: figure out why this isn't already the case data, state = _normalize_scan_shape(data, state) # process post_init hooks @@ -360,8 +386,8 @@ def initialize_reconstruction( def prepare_for_engine(patterns: Patterns, state: ReconsState, xp: t.Any, engine: EnginePlan) -> t.Tuple[Patterns, ReconsState]: # TODO: more graceful - if isinstance(engine, GradientEnginePlan) and not xp_is_jax(xp): - raise ValueError("The gradient descent engine requires the jax backend.") + if isinstance(engine, GradientEnginePlan) and not (xp_is_jax(xp) or xp_is_torch(xp)): + raise ValueError("The gradient descent engine requires the 'jax' or 'torch' backend.") state = state.to_xp(xp) @@ -429,7 +455,7 @@ def prepare_for_engine(patterns: Patterns, state: ReconsState, xp: t.Any, engine return patterns, state -_MISSING = object() +#_MISSING = object() def merge(left: t.Any, right: t.Any) -> t.Any: @@ -442,8 +468,8 @@ def _as_dict(val) -> t.Optional[dict]: return val.dict(set_only=True) return None - if left is _MISSING or right is _MISSING: - return left if right is _MISSING else right + if left is None or right is None: + return left if right is None else right if isinstance(left, Hook) and isinstance(right, Hook): if left.ref != right.ref: @@ -454,9 +480,9 @@ def _as_dict(val) -> t.Optional[dict]: if (left_d := _as_dict(left)) is not None and (right_d := _as_dict(right)) is not None: keys = set(left_d.keys()) | set(right_d.keys()) - return {k: merge(left_d.get(k, _MISSING), right_d.get(k, _MISSING)) for k in keys} + return {k: merge(left_d.get(k, None), right_d.get(k, None)) for k in keys} - return left if right is _MISSING else right + return left if right is None else right __all__ = [ diff --git a/phaser/hooks/__init__.py b/phaser/hooks/__init__.py index ff86e08..a77fa63 100644 --- a/phaser/hooks/__init__.py +++ b/phaser/hooks/__init__.py @@ -35,6 +35,13 @@ class LoadEmpadProps(Dataclass): adu: t.Optional[float] = None det_flips: t.Optional[t.Tuple[bool, bool, bool]] = None +class LoadGatanProps(Dataclass): + path: Path + + diff_step: t.Optional[float] = None + kv: t.Optional[float] = None + adu: t.Optional[float] = None + class LoadManualProps(Dataclass, kw_only=True): path: Path @@ -65,6 +72,7 @@ class LoadManualProps(Dataclass, kw_only=True): class RawDataHook(Hook[None, RawData]): known = { 'empad': ('phaser.hooks.io.empad:load_empad', LoadEmpadProps), + 'gatan': ('phaser.hooks.io.gatan:load_gatan', LoadGatanProps, ('rsciio',)), 'manual': ('phaser.hooks.io.manual:load_manual', LoadManualProps), } @@ -164,6 +172,14 @@ class ScaleProps(Dataclass): scale: float +class OffsetProps(Dataclass): + offset: float + + +class BinProps(Dataclass): + bin: int + + class CropDataProps(Dataclass): crop: t.Tuple[ # y_i, y_f, x_i, x_f @@ -189,6 +205,8 @@ class PostLoadHook(Hook[RawData, RawData]): 'crop_data': ('phaser.hooks.preprocessing:crop_data', CropDataProps), 'poisson': ('phaser.hooks.preprocessing:add_poisson_noise', PoissonProps), 'scale': ('phaser.hooks.preprocessing:scale_patterns', ScaleProps), + 'offset': ('phaser.hooks.preprocessing:offset_patterns', OffsetProps), + 'bin': ('phaser.hooks.preprocessing:bin_patterns', BinProps), } @@ -202,7 +220,7 @@ class PostInitHook(Hook[PostInitArgs, t.Tuple['Patterns', 'ReconsState']]): class EngineArgs(t.TypedDict): data: 'Patterns' state: 'ReconsState' - dtype: DTypeLike + dtype: t.Type[numpy.floating] xp: t.Any recons_name: str observer: 'Observer' diff --git a/phaser/hooks/_dependencies.py b/phaser/hooks/_dependencies.py new file mode 100644 index 0000000..2793c61 --- /dev/null +++ b/phaser/hooks/_dependencies.py @@ -0,0 +1,46 @@ +import abc +import importlib +import typing as t + +class Dependency(abc.ABC): + @abc.abstractmethod + def check(self): + ... + + @abc.abstractmethod + def install_instructions(self) -> str: + ... + + +class ImportDependency(Dependency): + def __init__(self, ref: str, install: str) -> None: + self.ref = ref + self.install = install + + def check(self): + importlib.import_module(self.ref) + + def install_instructions(self) -> str: + return self.install + + +def check_dependencies(dependencies: t.Sequence[str], hook: str): + if isinstance(dependencies, str): + dependencies = (dependencies,) + + for dependency in dependencies: + if (dep := _DEPENDENCIES.get(dependency)) is None: + raise RuntimeError(f"Unknown dependency '{dependency}'. This is likely a bug in the hook declaration.") + + try: + dep.check() + except Exception as e: + raise RuntimeError( + f"Missing dependency '{dependency}' requried by hook '{hook}'.\n" + f"To install: {dep.install_instructions()}" + ) from e + + +_DEPENDENCIES = { + 'rsciio': ImportDependency('rsciio', "'pip install rosettasciio' or 'conda install rosettasciio'"), +} \ No newline at end of file diff --git a/phaser/hooks/hook.py b/phaser/hooks/hook.py index f89827a..ccbdc67 100644 --- a/phaser/hooks/hook.py +++ b/phaser/hooks/hook.py @@ -4,7 +4,6 @@ import importlib import typing as t -import pane from pane.convert import ConverterHandlers, DataType from pane.converters import Converter, make_converter from pane.errors import ErrorNode, WrongTypeError, ParseInterrupt, ProductErrorNode @@ -13,15 +12,17 @@ U = t.TypeVar('U') class Hook(t.Generic[T, U], abc.ABC): - known: t.ClassVar[t.Dict[str, t.Tuple[str, type]]] = {} + known: t.ClassVar[t.Dict[str, t.Union[t.Tuple[str, type], t.Tuple[str, type, t.Tuple[str, ...]]]]] = {} def __init__( self, ref: str, props: t.Optional[t.Any] = None, type: t.Optional[str] = None, + dependencies: t.Tuple[str, ...] = () ): self.ref: str = ref self.type: t.Optional[str] = type self.f: t.Optional[t.Callable[..., U]] = None self.props: t.Optional[t.Any] = props + self.dependencies: t.Tuple[str, ...] = dependencies def func_ref(self) -> str: if self.type is not None: @@ -34,6 +35,10 @@ def _resolve_ref(self) -> t.Callable: return globals()[self.ref] raise ValueError(f"Can't resolve function reference '{self.ref}'.") + if self.dependencies is not None: + from ._dependencies import check_dependencies + check_dependencies(self.dependencies, self.func_ref()) + (module_path, func_name) = self.ref.split(':') try: module = importlib.import_module(module_path) @@ -42,10 +47,12 @@ def _resolve_ref(self) -> t.Callable: raise try: - return getattr(module, func_name) + f = getattr(module, func_name) except AttributeError: raise AttributeError(f"No function '{func_name}' found in module '{module_path}'") + return f + def resolve(self) -> t.Callable[..., U]: if self.f is None: self.f = self._resolve_ref() @@ -114,9 +121,13 @@ def try_convert(self, val: t.Any) -> Hook[T, U]: ref = str(val.pop('type')) props = val + dependencies = () + if ref in self.cls.known: ty = ref - (ref, props_ty) = self.cls.known[ty] + (ref, props_ty, *dep) = self.cls.known[ty] + if len(dep): + dependencies = dep[0] converter = make_converter(props_ty) props = converter.try_convert(props) @@ -125,7 +136,7 @@ def try_convert(self, val: t.Any) -> Hook[T, U]: else: ty = None - return self.cls(ref, props, ty) + return self.cls(ref, props, ty, dependencies=dependencies) def collect_errors(self, val: t.Any) -> t.Optional[ErrorNode]: try: @@ -143,7 +154,7 @@ def collect_errors(self, val: t.Any) -> t.Optional[ErrorNode]: if ref in self.cls.known: ty = ref - (ref, props_ty) = self.cls.known[ty] + (ref, props_ty, *dep) = self.cls.known[ty] converter = make_converter(props_ty) try: diff --git a/phaser/hooks/io/gatan.py b/phaser/hooks/io/gatan.py new file mode 100644 index 0000000..64071d3 --- /dev/null +++ b/phaser/hooks/io/gatan.py @@ -0,0 +1,98 @@ + +from pathlib import Path +import warnings +import logging +import typing as t + +import numpy + +from phaser.utils.num import Sampling +from phaser.utils.physics import Electron +from phaser.io.gatan import load_4d, GatanMetadata +from phaser.types import cast_length +from rsciio import digitalmicrograph as dm +from .. import LoadGatanProps, RawData + + +def load_gatan(args: None, props: LoadGatanProps) -> RawData: + logger = logging.getLogger(__name__) + + path = Path(props.path).expanduser() + + # file = dm.file_reader(path, lazy=True) + + # if path.suffix.lower() == '.json': # load as metadata + # pass # stub for now, not implemented + + # else: # grab from dm4 file + metadata = GatanMetadata.from_dm4(path) + + assert metadata.path is not None + + # path = metadata.path / metadata.gatan_filename + + voltage = props.kv * 1e3 if props.kv is not None else metadata.voltage + diff_step = props.diff_step or metadata.diff_step + scan_shape = metadata.scan_shape + + + print(f"Scan shape: {scan_shape}, Step size: {metadata.scan_step}") + adu = 1 #props.adu or meta.adu + needs_scale = not metadata.is_simulated() + + probe_hook = { + 'type': 'focused', + 'conv_angle': metadata.conv_angle, + 'defocus': metadata.defocus * 1e10 if metadata.defocus is not None else None, + } + # TODO: handle explicit scan_positions here + scan_hook = { + 'type': 'raster', + # [x, y] -> [y, x] + 'shape': tuple(reversed(metadata.scan_shape)), + 'step_size': tuple(s*1e10 for s in reversed(metadata.scan_step)), # m to A + 'rotation': metadata.scan_rotation or 0.0, + 'affine': metadata.scan_correction[::-1, ::-1] if metadata.scan_correction is not None else None, + } + + + + if metadata.voltage is None: + raise ValueError("'kv'/'voltage' must be specified by metadata or passed to 'raw_data'") + if metadata.diff_step is None: + raise ValueError("'diff_step' must be specified by metadata or passed to 'raw_data'") + + wavelength = Electron(voltage).wavelength + + if not path.exists(): + raise ValueError(f"Couldn't find gatan data at path {path}") + + patterns = numpy.fft.ifftshift(load_4d(path, scan_shape, memmap=False), axes=(-1, -2)).astype(numpy.float32) + + if needs_scale: + if metadata.e_scaling is None: + warnings.warn("ADU not supplied for experimental dataset. This is not recommended.") + else: + logger.info(f"Offsetting patterns by {metadata.background_offset:.3e} and scaling by {metadata.e_scaling:.5e}") + patterns -= metadata.background_offset + patterns *= metadata.e_scaling + + # patterns = numpy.transpose(patterns, (1, 0, 2, 3)) + + a = float(wavelength / (diff_step * 1e-3)) # recip. pixel size -> 1 / real space extent + + sampling = Sampling(cast_length(patterns.shape[-2:], 2), extent=(a, a)) + + mask = numpy.zeros_like(patterns, shape=patterns.shape[-2:]).astype(numpy.float32) + + mask[2:-2, 2:-2] = 1. + + return { + 'patterns': patterns, + 'mask': numpy.fft.ifftshift(mask, axes=(-1, -2)).astype(numpy.float32), + 'sampling': sampling, + 'wavelength': wavelength, + 'probe_hook': probe_hook, + 'scan_hook': scan_hook, + 'seed': None, + } \ No newline at end of file diff --git a/phaser/hooks/io/manual.py b/phaser/hooks/io/manual.py index 73c513c..c24d11f 100644 --- a/phaser/hooks/io/manual.py +++ b/phaser/hooks/io/manual.py @@ -124,6 +124,7 @@ def _normalize_key(key: str) -> t.Tuple[str, ...]: _HDF5_KNOWN_KEYS: t.List[t.Tuple[str, ...]] = [ ('dp',), ('data',), + ('datacube_root', 'datacube', 'data'), ] diff --git a/phaser/hooks/preprocessing.py b/phaser/hooks/preprocessing.py index 60b7813..5fc6dd3 100644 --- a/phaser/hooks/preprocessing.py +++ b/phaser/hooks/preprocessing.py @@ -10,7 +10,7 @@ from phaser.utils.misc import create_rng, create_sparse_groupings from phaser.utils.image import affine_transform from phaser.state import Patterns, ReconsState -from . import RawData, PostInitArgs, PoissonProps, ScaleProps, DropNanProps, CropDataProps +from . import RawData, PostInitArgs, PoissonProps, ScaleProps, DropNanProps, CropDataProps, OffsetProps, BinProps logger = logging.getLogger(__name__) @@ -38,6 +38,24 @@ def scale_patterns(raw_data: RawData, props: ScaleProps) -> RawData: raw_data['patterns'] *= props.scale return raw_data +def offset_patterns(raw_data: RawData, props: OffsetProps) -> RawData: + raw_data['patterns'] -= props.offset + return raw_data + +def bin_patterns(raw_data: RawData, props: BinProps) -> RawData: + xp = get_array_module(raw_data['patterns']) + bin_factor = props.bin + patterns = raw_data['patterns'] + Ny, Nx = patterns.shape[-2:] + patterns = patterns.reshape(*patterns.shape[:-2], + Ny // bin_factor, bin_factor, + Nx // bin_factor, bin_factor).sum(axis=(-1, -3)) + + print(patterns.shape) # (120, 45, 128, 128) + + raw_data['patterns'] = patterns + return raw_data + def add_poisson_noise(raw_data: RawData, props: PoissonProps) -> RawData: xp = get_array_module(raw_data['patterns']) @@ -59,7 +77,7 @@ def add_poisson_noise(raw_data: RawData, props: PoissonProps) -> RawData: logger.info(f"Mean pattern intensity: {numpy.nanmean(numpy.nansum(patterns, axis=(-1, -2)))}") - raw_data['patterns'] = xp.array(patterns) + raw_data['patterns'] = xp.asarray(patterns) return raw_data @@ -79,7 +97,7 @@ def drop_nan_patterns(args: PostInitArgs, props: DropNanProps) -> t.Tuple[Patter logger.info(f"Dropping {n}/{patterns.shape[0]} patterns which are at least {props.threshold:.1%} NaN values") patterns = patterns[~mask] - if scan.shape[0] == mask.size: + if scan.shape[0] == xp.size(mask): # apply mask to scan as well scan = scan[~mask] elif scan.shape[0] != patterns.shape[0]: @@ -111,7 +129,7 @@ def diffraction_align(args: PostInitArgs, props: t.Any = None) -> t.Tuple[Patter sum_pattern = xp.zeros(patterns.patterns.shape[-2:], dtype=patterns.patterns.dtype) for group in groups: - pats = xp.array(patterns.patterns[tuple(group)]) * xp.array(patterns.pattern_mask) + pats = xp.asarray(patterns.patterns[tuple(group)]) * xp.asarray(patterns.pattern_mask) sum_pattern += t.cast(NDArray[numpy.floating], xp.nansum(pats, axis=tuple(range(pats.ndim - 2)))) mean_pattern = sum_pattern / math.prod(patterns.patterns.shape[:-2]) diff --git a/phaser/hooks/regularization.py b/phaser/hooks/regularization.py index 03f0545..fe42b57 100644 --- a/phaser/hooks/regularization.py +++ b/phaser/hooks/regularization.py @@ -27,6 +27,9 @@ def apply_iter(self, sim: 'ReconsState', state: StateT) -> t.Tuple['ReconsState' @t.runtime_checkable class CostRegularizer(HasState[StateT], t.Protocol[StateT]): + def name(self) -> str: + ... + def calc_loss_group(self, group: NDArray[numpy.integer], sim: 'ReconsState', state: StateT) -> t.Tuple[Float, StateT]: ... diff --git a/phaser/hooks/scan.py b/phaser/hooks/scan.py index 6e402fb..a0b932f 100644 --- a/phaser/hooks/scan.py +++ b/phaser/hooks/scan.py @@ -1,4 +1,5 @@ +import logging import numpy from numpy.typing import NDArray @@ -9,20 +10,28 @@ def raster_scan(args: ScanHookArgs, props: RasterScanProps) -> NDArray[numpy.floating]: xp = cast_array_module(args['xp']) + logger = logging.getLogger(__name__) if props.shape is None: raise ValueError("scan 'shape' must be specified by metadata or manually") if props.step_size is None: raise ValueError("scan 'step_size' must be specified by metadata or manually") + step_size = numpy.broadcast_to(props.step_size, (2,)) + rot = props.rotation or 0.0 + if props.affine is not None: + affine = xp.asarray(props.affine, dtype=args['dtype']) + else: + affine = None + + logger.info(f"Making raster scan, shape {props.shape}," + f" step size [{step_size[0]:.2f}, {step_size[1]:.2f}]," + f" rotation {rot:.2f} deg" + f" affine transformation {affine.ravel() if affine is not None else 'None'}") + scan = make_raster_scan( - props.shape, props.step_size, props.rotation or 0.0, + props.shape, step_size, rot, affine, dtype=args['dtype'], xp=xp, ) - if props.affine is not None: - affine = xp.array(props.affine, dtype=scan.dtype) - # equivalent to (affine @ scan.T).T (active transformation) - scan = scan @ affine.T - - return scan \ No newline at end of file + return scan diff --git a/phaser/hooks/schedule.py b/phaser/hooks/schedule.py index ceac461..017abe0 100644 --- a/phaser/hooks/schedule.py +++ b/phaser/hooks/schedule.py @@ -3,7 +3,7 @@ import numpy -from ..types import Dataclass, Flag, process_schedule +from ..types import Dataclass, SimpleFlag, process_schedule from .hook import Hook if t.TYPE_CHECKING: @@ -23,7 +23,9 @@ class ScheduleHook(Hook[FlagArgs, float]): known = {} -FlagLike: t.TypeAlias = t.Union[bool, Flag, FlagHook] +Flag: t.TypeAlias = t.Callable[['FlagArgs'], bool] +Schedule: t.TypeAlias = t.Callable[['FlagArgs'], float] +FlagLike: t.TypeAlias = t.Union[bool, SimpleFlag, FlagHook] ScheduleLike: t.TypeAlias = t.Union[float, ScheduleHook] diff --git a/phaser/io/gatan.py b/phaser/io/gatan.py new file mode 100644 index 0000000..0cc6d10 --- /dev/null +++ b/phaser/io/gatan.py @@ -0,0 +1,298 @@ + +from pathlib import Path +import re +import typing as t + +import numpy +import pane +import pane.io +from numpy.typing import NDArray +from pane.annotations import shape +from pane.convert import IntoConverterHandlers, from_data +from typing_extensions import Self +from rsciio import digitalmicrograph as dm + +from numpy import flip +import numpy as np + + +from phaser.utils.physics import Electron + +from phaser.types import IsVersion + + +def _get_dir(f: pane.io.FileOrPath) -> t.Optional[Path]: + if isinstance(f, (str, Path)): + return Path(f).parent + + name = getattr(f, 'name', None) + if name in (None, '', ''): + return None + path = Path(name) + return path.parent if path.exists() else None + + +class GatanMetadata(pane.PaneBase, frozen=False, kw_only=True, allow_extra=True): + file_type: t.Literal['gatan_file'] = 'gatan_file' + + @classmethod + def from_dm4(cls, f: pane.io.FileOrPath, *, + custom: t.Optional[IntoConverterHandlers] = None) -> Self: + + path = _get_dir(f) + + file = dm.file_reader(f, lazy=True) + + metadata = {'file_type':'gatan_file', + 'name':str(f.stem), + 'gatan_filename':str(f.stem), + 'raw':str(f.suffix.removeprefix('.')), + 'orig_path':str(f.parent.absolute()), + 'author': None, + 'voltage': 0.0, + 'conv_angle': 0.0, + 'defocus': 1.0e-10, + 'camera_length': 0.0, + 'diff_step': 0.0, + 'e-scaling': 1.0, + 'background_offset': 0.0, + 'scan_rotation': 0.0, + 'detector_shape': [0, 0], + 'scan_shape': [0, 0], + 'scan_fov': [0, 0], + 'scan_step': [0, 0], + 'exposure_time': 0.0, + 'post_exposure_time': 0.0, + 'beam_current': 0.0, + 'scan_correction': None, + 'diff_transpose': [False, False, False], #diagonal, horizontal, vertical + 'scan_transpose': [False, False, False], #diagonal, horizontal, vertical + 'notes': None, + 'crop': None + } + + gatan_metadata = file[0]['original_metadata']['ImageList']['TagGroup0']['ImageTags'] # first image metadata + imagedata_metadata = file[0]['original_metadata']['ImageList']['TagGroup0']['ImageData'] + digiscan_metadata = file[0]['original_metadata']['ImageList']['TagGroup0']['ImageTags']['DigiScan'] + + + e_calibration = imagedata_metadata['Calibrations']['Brightness'] + metadata['e_scaling'] = e_calibration['Scale'] + metadata['background_offset'] = e_calibration['Origin'] + + calibrations = list(imagedata_metadata['Calibrations']['Dimension'].values()) + + diff_calibrations = calibrations[0:2] + real_calibrations = calibrations[2:4] + data_dim = list(imagedata_metadata['Dimensions'].values()) + + cam_acq = gatan_metadata['Acquisition'] + microscope = gatan_metadata['Microscope Info'] + + metadata['diff_transpose'] = [bool(s) for s in cam_acq['Device']['Configuration']['Transpose'].values()] + + SI_info = gatan_metadata['SI'] + + metadata['time'] = SI_info['Acquisition']['Date'] + metadata['scan_shape'] = [int(s) for s in SI_info['Acquisition']['Spatial Sampling'].values()] + metadata['exposure_time'] = SI_info['Acquisition']['Pixel time (s)'] + + metadata['camera_length'] = microscope['STEM Camera Length'] + metadata['voltage'] = microscope['Voltage'] + + + # diffraction step + + units = diff_calibrations[0]['Units'] + + diff_scale = 1 + + wavelength = float(Electron(metadata['voltage']).wavelength*1e-10) + + + if units == '1/nm': + diff_scale = 1e9 + elif units == '1/um': + diff_scale = 1e6 + elif units == '1/pm': + diff_scale = 1e12 + + metadata['diff_step'] = diff_calibrations[0]['Scale']*diff_scale*wavelength*1e3 # to mrad + + scan_steps = [real_calibrations[0]['Scale'], real_calibrations[1]['Scale']] + + # Need to implement error handling, not sure what dm might record as units + units = real_calibrations[0]['Units'] + scan_scale = 1 + + + if units == 'nm': + scan_scale = float(1e-9) + elif units == 'um': + scan_scale = 1e-6 + elif units == 'pm': + scan_scale = float(1e-12) + + + + metadata['scan_step'] = [scan_scale*scan_step for scan_step in scan_steps] + metadata['scan_rotation'] = digiscan_metadata['Rotation'] + + metadata['detector_shape'] = [data_dim[1], data_dim[0]] + metadata['scan_shape'] = [data_dim[2], data_dim[3]] + metadata['scan_fov'] = [metadata['scan_shape'][0]*metadata['scan_step'][0], metadata['scan_shape'][1]*metadata['scan_step'][1]] + + self = from_data(metadata, cls, custom=custom) + + object.__setattr__(self, 'path', path) + return self + + + def __post_init__(self): + object.__setattr__(self, 'path', None) + + name: str + """Experiment name""" + + version: t.Annotated[str, IsVersion(exactly="1.0")] = "1.0" + """Gatan Metadata version""" + + gatan_filename: str + # """Gatan 4DSTEM data filename, relative to metadata location.""" + + path: t.Optional[Path] = pane.field(init=False, exclude=True) + """Current path to experimental folder (based on metadata loading)""" + + voltage: float + """Accelerating voltage (V).""" + conv_angle: t.Optional[float] = None + """Convergence angle (mrad).""" + defocus: t.Optional[float] = None + """Defocus (m). Positive is overfocus.""" + camera_length: t.Optional[float] = None + """Camera length (m).""" + diff_step: t.Optional[float] = None + """Diffraction pixel size (mrad/px).""" + + scan_rotation: float + """Scan rotation (degrees).""" + scan_shape: t.Tuple[int, int] + """Scan shape (x, y).""" + scan_fov: t.Tuple[float, float] + """Scan field of view (m).""" + scan_step: t.Tuple[float, float] + """Scan step (m/px).""" + + exposure_time: t.Optional[float] = None + """Pixel exposure time (s).""" + # post_exposure_time: t.Optional[float] = None + # """Pixel post-exposure time (s).""" + beam_current: t.Optional[float] = None + """Approx. beam current (A).""" + e_scaling:t.Optional[float] = 1.0 + + """Single-electron scaling ).""" + background_offset:t.Optional[float] = 0.0 + + scan_correction: t.Optional[t.Annotated[NDArray[numpy.floating], shape((2, 2))]] = None + """Scan correction matrix, [x', y'] = scan_correction @ [x, y]""" + + scan_positions: t.Optional[t.List[t.Tuple[float, float]]] = None + """ + Scan position override (m). + Should be specified as a 1d list of (x, y) positions, in scan order. `scan_correction` is applied to these positions (if present). + """ + + notes: t.Optional[str] = None + + crop: t.Optional[t.Tuple[int, int, int, int]] = None + """Region scan is valid within, (min_y, max_y, min_x, max_x). Python-style slicing.""" + + def is_simulated(self) -> bool: + return self.file_type == "pyMultislicer_metadata" + + +def load_4d(path: t.Union[str, Path], scan_shape: t.Optional[t.Tuple[int, int]] = None, + memmap: bool = False) -> NDArray[numpy.float32]: + """ + Load a gatan dm4 dataset into memory. + + The file is loaded so the dimensions are: (scan_y, scan_x, k_y, k_x), with y decreasing downwards. + + Patterns are not fftshifted or normalized upon loading. + + # Parameters + + - `path`: Path to file to load + - `scan_shape`: Scan shape of dataset. Will be inferred from the filename if not specified. + - `memmap`: If specified, memmap the file as opposed to loading it eagerly. + + Returns a numpy array (or `numpy.memmap`) + """ + path = Path(path) + + n_y, n_x = scan_shape + + if memmap: + a = dm.file_reader(path, lazy=True)[0]['data'] + else: + a = dm.file_reader(path, lazy=False)[0]['data'] + + if a.shape[0]*a.shape[1] != n_x * n_y: + raise ValueError(f"Got {a.shape[0]*a.shape[1]} probes, expected {n_x}x{n_y} = {n_x * n_y}.") + + return a.astype(numpy.float32) + + +@t.overload +def save_4d(arr: NDArray[numpy.float32], *, path: t.Union[str, Path], folder: None = None, name: None = None): + ... + +@t.overload +def save_4d(arr: NDArray[numpy.float32], *, path: None = None, folder: t.Union[str, Path], name: t.Optional[str] = None): + ... + +def save_4d(arr: NDArray[numpy.float32], *, path: t.Union[str, Path, None] = None, + folder: t.Union[str, Path, None] = None, name: t.Optional[str] = None): #): + """ + Save a raw EMPAD dataset. + + Either `path` or `folder` can be specified. If `folder` is specified, + `name` will be used as a format string to determine the filename. + `path` and `folder` cannot be specified simultaneously. + + Patterns are not fftshifted or normalized upon saving. + + Parameters: + - `arr`: Array to save + - `path`: Path to save dataset to. + - `folder`: Folder to save dataset inside. + - `name`: When `folder` is specified, format to use to determine filename. Defaults to `"scan_x{x}_y{y}.raw"`. + Will be formatted using the scan shape `{'x': n_x, 'y': n_y}`. + """ + + try: + assert len(arr.shape) == 4 + assert arr.shape[2:] == (128, 128) + except AssertionError as e: + raise ValueError("Invalid data format") from e + + if folder is not None: + if path is not None: + raise ValueError("Cannot specify both 'path' and 'folder'") + + n_y, n_x = arr.shape[:2] + path = Path(folder) / (name or "scan_x{x}_y{y}.raw").format(x=n_x, y=n_y) + elif path is not None: + path = Path(path) + else: + raise ValueError("Must specify either 'path' or 'folder'") + + out_shape = list(arr.shape) + out_shape[2] = 130 # dead rows + + out = numpy.zeros(out_shape, dtype=numpy.float32) + out[..., 127::-1, :] = arr.astype(numpy.float32) + + with open(path, 'wb') as f: + out.tofile(f) diff --git a/phaser/observer.py b/phaser/observer.py index cfba6d4..dbb9f53 100644 --- a/phaser/observer.py +++ b/phaser/observer.py @@ -6,7 +6,7 @@ import typing as t from phaser.plan import ReconsPlan, EnginePlan, SaveOptions -from phaser.state import ReconsState, PartialReconsState +from phaser.state import ReconsState, PartialReconsState, ProgressState from phaser.types import EarlyTermination, flag_any_true, process_flag if t.TYPE_CHECKING: @@ -43,7 +43,7 @@ def update_group(self, state: t.Union[ReconsState, PartialReconsState], force: b """Called when a group is finished, with updated reconstruction state.""" pass - def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional[float] = None): + def update_iteration(self, state: ReconsState, i: int, n: int, errors: t.Dict[str, float]): """Called when an iteration is finished, with updated reconstruction state.""" pass @@ -75,6 +75,8 @@ def __init__(self): self.init_start_time: t.Optional[float] = None self.recons_start_time: t.Optional[float] = None + self.init_start_utc: t.Optional[float] = None + self.recons_start_utc: t.Optional[float] = None self.engine_start_time: t.Optional[float] = None self.iter_start_time: t.Optional[float] = None @@ -87,12 +89,17 @@ def _format_mmss(self, seconds: float) -> str: mm, ss = divmod(seconds, 60) return f"{int(mm):02d}:{ss:06.3f}" + def get_utc(self) -> float: + return time.time_ns() * 1e-9 + def init_recons(self, plan: ReconsPlan): self.logger.info("Initializing reconstruction...") self.init_start_time = time.monotonic() + self.init_start_utc = self.get_utc() def start_recons(self, init_state: ReconsState): self.recons_start_time = time.monotonic() + self.recons_start_utc = self.get_utc() if self.init_start_time is not None: delta = self.recons_start_time - self.init_start_time @@ -100,6 +107,19 @@ def start_recons(self, init_state: ReconsState): else: self.logger.info("Initialized reconstruction") + if init_state.iter.total_iter == 0: + utc_prog = ProgressState() + + if self.init_start_utc is not None: + utc_prog.iters.append(-1) + utc_prog.values.append(self.init_start_utc) + utc_prog.iters.append(0) + utc_prog.values.append(self.recons_start_utc) + init_state.progress['utc'] = utc_prog + + if self.init_start_time is not None: + init_state.progress['time'] = ProgressState([0], [self.recons_start_time - self.init_start_time]) + def init_engine( self, init_state: ReconsState, *, recons_name: str, plan: EnginePlan, **kwargs: t.Any @@ -111,7 +131,7 @@ def start_engine(self, init_state: ReconsState): self.logger.info("Engine initialized") self.iter_start_time = time.monotonic() - def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional[float] = None): + def update_iteration(self, state: ReconsState, i: int, n: int, errors: t.Dict[str, float]): finish_time = time.monotonic() if self.iter_start_time is not None: @@ -122,10 +142,19 @@ def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional w = len(str(n)) - error_s = f" Error: {error:.3e}" if error is not None else "" - self.logger.info(f"Finished iter {i:{w}}/{n}{time_s}{error_s}") + error_s = f" Error: {error:.3e}" if (error := errors.get('total_loss')) else "" + other_errors = ", ".join(f"{k}: {v:.3e}" for (k, v) in errors.items() if k != 'total_loss') + other_errors = f"\n Error breakdown: {other_errors}" if other_errors else "" + self.logger.info(f"Finished iter {i:{w}}/{n}{time_s}{error_s}{other_errors}") self.iter_start_time = finish_time + if 'utc' in state.progress: + state.progress['utc'].iters.append(int(state.iter.total_iter)) + state.progress['utc'].values.append(self.get_utc()) + if 'time' in state.progress and self.init_start_time is not None: + state.progress['time'].iters.append(int(state.iter.total_iter)) + state.progress['time'].values.append(finish_time - self.init_start_time) + def finish_engine(self, state: ReconsState): self.logger.info("Engine finished!") if self.engine_start_time is not None: @@ -155,12 +184,12 @@ def init_engine( self.no_improvement_iter = 0 def _error_from_state(self, state: t.Union[ReconsState, PartialReconsState]) -> t.Optional[float]: - if state.progress is None or state.progress.detector_errors.size == 0: + if state.progress is None or (progress := state.progress['total_loss']) is None or not len(progress.values): return None - return state.progress.detector_errors[-1] + return progress.values[-1] - def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional[float] = None): - if (error := self._error_from_state(state)) is None: + def update_iteration(self, state: ReconsState, i: int, n: int, errors: t.Dict[str, float]): + if (error := errors.get('total_loss')) is None: return if self.best_error is None or error < self.best_error: @@ -228,7 +257,7 @@ def init_engine( (self.out_dir / 'finished').unlink(missing_ok=True) - def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional[float] = None): + def update_iteration(self, state: ReconsState, i: int, n: int, errors: t.Dict[str, float]): from phaser.engines.common.output import output_images, output_state assert self.out_dir is not None @@ -303,7 +332,7 @@ def update_group(self, state: t.Union[ReconsState, PartialReconsState], force: b ... @_fwd_to_children - def update_iteration(self, state: ReconsState, i: int, n: int, error: t.Optional[float] = None): + def update_iteration(self, state: ReconsState, i: int, n: int, errors: t.Dict[str, float]): """Called when an iteration is finished, with updated reconstruction state.""" ... diff --git a/phaser/plan.py b/phaser/plan.py index 1326638..8690eae 100644 --- a/phaser/plan.py +++ b/phaser/plan.py @@ -1,7 +1,7 @@ from pathlib import Path import typing as t -from .types import Dataclass, Slices, BackendName, Flag, ReconsVars, IsVersion, EmptyDict +from .types import Dataclass, Slices, BackendName, SimpleFlag, ReconsVars, IsVersion, EmptyDict from .hooks import RawDataHook, ProbeHook, ObjectHook, ScanHook, EngineHook, PostInitHook, PostLoadHook, TiltHook from .hooks.solver import NoiseModelHook, ConventionalSolverHook, PositionSolverHook, GradientSolverHook from .hooks.schedule import FlagLike, ScheduleLike @@ -64,7 +64,7 @@ class EnginePlan(Dataclass, kw_only=True): update_positions: FlagLike = False update_tilt: FlagLike = False - calc_error: FlagLike = Flag(every=1) + calc_error: FlagLike = SimpleFlag(every=1) calc_error_fraction: float = 0.1 save: FlagLike = False @@ -180,6 +180,7 @@ class ReconsPlan(Dataclass, kw_only=True): name: str backend: t.Optional[BackendName] = None + device: t.Optional[str] = None dtype: t.Literal['float32', 'float64'] = 'float32' wavelength: t.Optional[float] = None diff --git a/phaser/state.py b/phaser/state.py index f09b98a..5502a74 100644 --- a/phaser/state.py +++ b/phaser/state.py @@ -5,16 +5,16 @@ from typing_extensions import Self from phaser.utils.num import Sampling, to_numpy, get_array_module, Float -from phaser.utils.misc import jax_dataclass +from phaser.utils.tree import tree_dataclass, field from phaser.utils.object import ObjectSampling if t.TYPE_CHECKING: from phaser.utils.io import HdfLike - from phaser.utils.image import _BoundaryMode + from phaser.utils.image import _InterpBoundaryMode from phaser.observer import Observer, ObserverSet -@jax_dataclass +@tree_dataclass class Patterns(): patterns: NDArray[numpy.floating] """Raw diffraction patterns, with 0-frequency sample in corner""" @@ -27,7 +27,7 @@ def to_numpy(self) -> Self: ) -@jax_dataclass +@tree_dataclass class IterState(): engine_num: int """Engine number. 1-indexed (0 means before any reconstruction).""" @@ -57,7 +57,7 @@ def empty() -> 'IterState': return IterState(0, 0, 0) -@jax_dataclass(static_fields=('sampling',)) +@tree_dataclass(static_fields=('sampling',)) class ProbeState(): sampling: Sampling """Probe coordinate system. See `Sampling` for more details.""" @@ -68,7 +68,7 @@ def resample( self, new_samp: Sampling, rotation: float = 0.0, order: int = 1, - mode: '_BoundaryMode' = 'grid-constant', + mode: '_InterpBoundaryMode' = 'grid-constant', ) -> Self: new_data = self.sampling.resample( self.data, new_samp, @@ -80,7 +80,7 @@ def resample( def to_xp(self, xp: t.Any) -> Self: return self.__class__( - self.sampling, xp.array(self.data) + self.sampling, xp.asarray(self.data) ) def to_numpy(self) -> Self: @@ -93,7 +93,7 @@ def copy(self) -> Self: return copy.deepcopy(self) -@jax_dataclass(static_fields=('sampling',)) +@tree_dataclass(static_fields=('sampling',)) class ObjectState(): sampling: ObjectSampling """Object coordinate system. See `ObjectSampling` for more details.""" @@ -107,7 +107,7 @@ class ObjectState(): def to_xp(self, xp: t.Any) -> Self: return self.__class__( - self.sampling, xp.array(self.data), xp.array(self.thicknesses) + self.sampling, xp.asarray(self.data), xp.asarray(self.thicknesses) ) def to_numpy(self) -> Self: @@ -118,7 +118,7 @@ def to_numpy(self) -> Self: def zs(self) -> NDArray[numpy.floating]: xp = get_array_module(self.thicknesses) if len(self.thicknesses) < 2: - return xp.array([0.], dtype=self.thicknesses.dtype) + return xp.asarray([0.], dtype=self.thicknesses.dtype) return xp.cumsum(self.thicknesses) - self.thicknesses def copy(self) -> Self: @@ -126,43 +126,19 @@ def copy(self) -> Self: return copy.deepcopy(self) -@jax_dataclass +@tree_dataclass class ProgressState: - iters: NDArray[numpy.integer] + iters: t.List[int] = field(default_factory=list) """Iterations error measurements were taken at.""" - detector_errors: NDArray[numpy.floating] + values: t.List[float] = field(default_factory=list) """Detector error measurements at those iterations""" - def to_numpy(self) -> Self: - return self.__class__( - to_numpy(self.iters), to_numpy(self.detector_errors) - ) - def copy(self) -> Self: import copy return copy.deepcopy(self) - @staticmethod - def empty() -> 'ProgressState': - return ProgressState( - numpy.array([], dtype=numpy.uint64), - numpy.array([], dtype=numpy.float64), - ) - - # TODO: this is a hack to prevent JIT recompilation. - def __hash__(self) -> int: - return id(self) - - def __eq__(self, other: t.Any) -> bool: - if type(self) is not type(other): - return False - xp = get_array_module(self.iters, other.iters) - return ( - xp.array_equal(self.iters, other.iters) and - xp.array_equal(self.detector_errors, other.detector_errors) - ) -@jax_dataclass(kw_only=True, static_fields=('progress',)) +@tree_dataclass(kw_only=True, drop_fields=('progress',)) class ReconsState: iter: IterState wavelength: Float @@ -173,15 +149,15 @@ class ReconsState: """Scan coordinates (y, x), in length units. Shape (..., 2)""" tilt: t.Optional[NDArray[numpy.floating]] = None """Tilt angles (y, x) per scan position, in mrad. Shape (..., 2)""" - progress: ProgressState + progress: t.Dict[str, ProgressState] = field(default_factory=dict) def to_xp(self, xp: t.Any) -> Self: return self.__class__( iter=self.iter, probe=self.probe.to_xp(xp), object=self.object.to_xp(xp), - scan=xp.array(self.scan), - tilt=None if self.tilt is None else xp.array(self.tilt), + scan=xp.asarray(self.scan), + tilt=None if self.tilt is None else xp.asarray(self.tilt), progress=self.progress, wavelength=self.wavelength, ) @@ -193,7 +169,7 @@ def to_numpy(self) -> Self: object=self.object.to_numpy(), scan=to_numpy(self.scan), tilt=None if self.tilt is None else to_numpy(self.tilt), - progress=self.progress.to_numpy(), + progress=self.progress, wavelength=float(self.wavelength), ) @@ -211,7 +187,7 @@ def read_hdf5(file: 'HdfLike') -> 'ReconsState': return hdf5_read_state(file).to_complete() -@jax_dataclass(kw_only=True, static_fields=('progress',)) +@tree_dataclass(kw_only=True, static_fields=('progress',)) class PartialReconsState: iter: t.Optional[IterState] = None wavelength: t.Optional[Float] = None @@ -221,7 +197,7 @@ class PartialReconsState: scan: t.Optional[NDArray[numpy.floating]] = None """Scan coordinates (y, x), in length units. Shape (..., 2)""" tilt: t.Optional[NDArray[numpy.floating]] = None - progress: t.Optional[ProgressState] = None + progress: t.Optional[t.Dict[str, ProgressState]] = None def to_numpy(self) -> Self: return self.__class__( @@ -230,8 +206,8 @@ def to_numpy(self) -> Self: object=self.object.to_numpy() if self.object is not None else None, scan=to_numpy(self.scan) if self.scan is not None else None, tilt=to_numpy(self.tilt) if self.tilt is not None else None, - progress=self.progress.to_numpy() if self.progress is not None else None, wavelength=float(self.wavelength) if self.wavelength is not None else None, + progress=self.progress, ) def to_complete(self) -> ReconsState: @@ -239,7 +215,7 @@ def to_complete(self) -> ReconsState: if len(missing): raise ValueError(f"ReconsState missing {', '.join(map(repr, missing))}") - progress = self.progress if self.progress is not None else ProgressState.empty() + progress = self.progress if self.progress is not None else {} iter = self.iter if self.iter is not None else IterState.empty() return ReconsState( @@ -260,7 +236,7 @@ def read_hdf5(file: 'HdfLike') -> 'PartialReconsState': return hdf5_read_state(file) -@jax_dataclass(static_fields=('name', 'observer')) +@tree_dataclass(static_fields=('name', 'observer')) class PreparedRecons: patterns: Patterns state: ReconsState diff --git a/phaser/types.py b/phaser/types.py index 5714612..3f1006f 100644 --- a/phaser/types.py +++ b/phaser/types.py @@ -12,7 +12,7 @@ if t.TYPE_CHECKING: from phaser.state import ReconsState - from phaser.hooks.schedule import FlagArgs, FlagLike, ScheduleLike + from phaser.hooks.schedule import FlagArgs, FlagLike, Flag, ScheduleLike, Schedule T = t.TypeVar('T') @@ -75,7 +75,7 @@ def __hash__(self) -> int: return hash(self.__class__.__name__) -BackendName: t.TypeAlias = t.Literal['cuda', 'cupy', 'jax', 'cpu', 'numpy'] +BackendName: t.TypeAlias = t.Literal['cupy', 'jax', 'torch', 'numpy'] ReconsVar: t.TypeAlias = t.Literal['object', 'probe', 'positions', 'tilt'] ReconsVars: t.TypeAlias = t.Annotated[t.FrozenSet[ReconsVar], _ReconsVarsAnnotation()] @@ -122,7 +122,7 @@ def thicknesses(self) -> t.List[float]: Slices: t.TypeAlias = t.Union[SliceList, SliceStep, SliceTotal] -class Flag(Dataclass): +class SimpleFlag(Dataclass): after: int = 0 every: int = 1 before: t.Optional[int] = None @@ -154,21 +154,21 @@ def __call__(self, args: 'FlagArgs') -> bool: @lru_cache -def process_flag(flag: 'FlagLike') -> t.Callable[['FlagArgs'], bool]: +def process_flag(flag: 'FlagLike') -> 'Flag': if isinstance(flag, bool): return _ConstFlag(flag) return flag @lru_cache -def process_schedule(schedule: 'ScheduleLike') -> t.Callable[['FlagArgs'], float]: +def process_schedule(schedule: 'ScheduleLike') -> 'Schedule': if isinstance(schedule, (int, float)): return lambda _: schedule return schedule def flag_any_true(flag: t.Callable[['FlagArgs'], bool], niter: int) -> bool: - if isinstance(flag, Flag): + if isinstance(flag, SimpleFlag): return flag.any_true(niter) elif isinstance(flag, _ConstFlag): return flag.val diff --git a/phaser/utils/_cuda_kernels.py b/phaser/utils/_cuda_kernels.py index 6c85523..b5e6d44 100644 --- a/phaser/utils/_cuda_kernels.py +++ b/phaser/utils/_cuda_kernels.py @@ -1,10 +1,13 @@ import functools +import re import typing as t import cupy # pyright: ignore[reportMissingImports] import numpy +from phaser.utils.misc import _MockModule + # grid # block # thread @@ -211,3 +214,34 @@ def _get_cutout_kernel(dtype: numpy.dtype, operation: str) -> cupy.RawKernel: """, kernel_name) kernel.compile() return kernel + + +def get_devices() -> t.Tuple[str, ...]: + n: int = cupy.cuda.runtime.getDeviceCount() + return tuple(f'cuda:{i}' for i in range(n)) + + +def to_device(device: t.Union[str, int, cupy.cuda.Device]) -> cupy.cuda.Device: + if isinstance(device, (int, cupy.cuda.Device)): + return cupy.cuda.Device(device) + device = str(device) + if (match := re.fullmatch(r'cuda:(\d)+', device)): + return cupy.cuda.Device(int(match[1])) + raise ValueError(f"Invalid device '{device}'") + + +def set_default_device(device: cupy.cuda.Device): + if not isinstance(device, cupy.cuda.Device): + raise TypeError(f"Invalid device '{device}' for backend cupy") + device.use() + + +def _wrap_call(f, *args: t.Any, **kwargs: t.Any) -> t.Any: + if (device := kwargs.pop('device', None)) is not None: + with to_device(device): + return f(*args, **kwargs) + + return f(*args, **kwargs) + + +mock_cupy = _MockModule(cupy, {}, _wrap_call) \ No newline at end of file diff --git a/phaser/utils/_jax_kernels.py b/phaser/utils/_jax_kernels.py index a0be3be..b0fcc0c 100644 --- a/phaser/utils/_jax_kernels.py +++ b/phaser/utils/_jax_kernels.py @@ -7,6 +7,9 @@ import jax.numpy as jnp # pyright: ignore[reportMissingImports] +Device: t.TypeAlias = t.Any + + def to_nd(arr: jax.Array, n: int) -> jax.Array: if arr.ndim > n: arr = arr.reshape(-1, *arr.shape[arr.ndim - n + 1:]) @@ -99,4 +102,49 @@ def affine_transform( return jax.vmap( lambda a: jax.scipy.ndimage.map_coordinates(a, tuple(coords), order=order, mode=jax_mode, cval=cval), - )(to_nd(input, n_axes + 1)).reshape((*input.shape[:-n_axes], *output_shape)) \ No newline at end of file + )(to_nd(input, n_axes + 1)).reshape((*input.shape[:-n_axes], *output_shape)) + + +def get_devices() -> t.Tuple[Device, ...]: + devices = [] + + for backend in ('gpu', 'tpu', 'cpu'): + try: + devices.extend(jax.devices(backend)) + except RuntimeError: + pass + + return tuple(devices) + + +def to_device(device: t.Union[str, Device]) -> Device: + if isinstance(device, jax.Device): + return device + + split = device.rsplit(':', maxsplit=1) + if len(split) == 1: + [backend] = split + index = 0 + else: + [backend, index] = split + index = int(index) + + try: + backend_devices = jax.devices(backend) + except RuntimeError: + raise RuntimeError(f"Can't use device '{device}': jax backend '{backend}' is unavailable") + + try: + return backend_devices[index] + except IndexError: + pass + if len(backend_devices) == 0: + raise RuntimeError(f"Can't use device '{device}': No available devices on jax backend '{backend}'") + raise RuntimeError(f"Can't use device '{device}': Device index {index} not available" + f" ({len(backend_devices)} device(s) on jax backend '{backend}')") + + +def set_default_device(device: Device): + if not isinstance(device, jax.Device): + raise TypeError(f"Invalid device '{device}' for backend jax") + jax.config.update('jax_default_device', device) \ No newline at end of file diff --git a/phaser/utils/_torch_kernels.py b/phaser/utils/_torch_kernels.py new file mode 100644 index 0000000..d8d2675 --- /dev/null +++ b/phaser/utils/_torch_kernels.py @@ -0,0 +1,449 @@ +import functools +import itertools +import operator +from types import ModuleType +import typing as t + +import numpy +from numpy.typing import ArrayLike +import torch + +from phaser.utils.num import _PadMode +from phaser.utils.image import _InterpBoundaryMode +from phaser.utils.misc import _MockModule + + +def get_cutouts(obj: torch.Tensor, start_idxs: torch.Tensor, cutout_shape: t.Tuple[int, int]) -> torch.Tensor: + #out_shape = (*start_idxs.shape[:-1], *obj.shape[:-2], *cutout_shape) + ys, xs = torch.arange(cutout_shape[0]), torch.arange(cutout_shape[1]) + yy, xx = torch.meshgrid(ys, xs, indexing='ij') + yy = start_idxs[..., 0][..., None, None] + yy + xx = start_idxs[..., 1][..., None, None] + xx + + out = obj[..., yy, xx] + if obj.ndim > 2: + # oof + out = torch.permute(out, (*(i + obj.ndim - 2 for i in range(start_idxs.ndim - 1)), *range(obj.ndim - 2), -2, -1)) + #assert out.shape == out_shape + return out + + +class _MockTensor(torch.Tensor): + #@property + #def dtype(self) -> t.Type[numpy.generic]: + # return to_numpy_dtype(super().dtype) + + @property + def T(self) -> '_MockTensor': # pyright: ignore[reportIncompatibleVariableOverride] + if self.ndim <= 2: + return _MockTensor(super().T) + return t.cast(_MockTensor, self.permute(*range(self.ndim - 1, -1, -1))) + + def astype(self, dtype: t.Union[str, torch.dtype, t.Type[numpy.generic]]) -> '_MockTensor': + return t.cast(_MockTensor, self.to(to_torch_dtype(dtype))) + + +_TORCH_TO_NUMPY_DTYPE: t.Dict[torch.dtype, t.Type[numpy.generic]] = { + torch.bool : numpy.bool_, + torch.uint8 : numpy.uint8, + torch.int8 : numpy.int8, + torch.int16 : numpy.int16, + torch.int32 : numpy.int32, + torch.int64 : numpy.int64, + torch.float16 : numpy.float16, + torch.float32 : numpy.float32, + torch.float64 : numpy.float64, + torch.complex64 : numpy.complex64, + torch.complex128 : numpy.complex128, +} + +_NUMPY_TO_TORCH_DTYPE: t.Dict[t.Type[numpy.generic], torch.dtype] = { + numpy.bool_ : torch.bool, + numpy.uint8 : torch.uint8, + numpy.int8 : torch.int8, + numpy.int16 : torch.int16, + numpy.int32 : torch.int32, + numpy.int64 : torch.int64, + numpy.float16 : torch.float16, + numpy.float32 : torch.float32, + numpy.float64 : torch.float64, + numpy.complex64 : torch.complex64, + numpy.complex128 : torch.complex128, +} + + +def to_torch_dtype(dtype: t.Union[str, torch.dtype, numpy.dtype, t.Type[numpy.generic]]) -> torch.dtype: + if isinstance(dtype, torch.dtype): + return dtype + if isinstance(dtype, numpy.dtype): + dtype = dtype.type + elif not isinstance(dtype, type) or not issubclass(dtype, numpy.generic): + dtype = numpy.dtype(dtype).type + + try: + return _NUMPY_TO_TORCH_DTYPE[dtype] + except KeyError: + raise ValueError(f"Can't convert dtype '{dtype}' to a PyTorch dtype") + + +def to_numpy_dtype(dtype: t.Union[str, torch.dtype, numpy.dtype, t.Type[numpy.generic]]) -> t.Type[numpy.generic]: + if isinstance(dtype, str): + return numpy.dtype(dtype).type + if isinstance(dtype, numpy.dtype): + return dtype.type + if isinstance(dtype, torch.dtype): + return _TORCH_TO_NUMPY_DTYPE[dtype] + return dtype + + +def _mirror(idx: torch.Tensor, size: int) -> torch.Tensor: + s = size -1 + return torch.abs((idx + s) % (2 * s) - s) + + +_BOUNDARY_FNS: t.Dict[str, t.Callable[[torch.Tensor, int], torch.Tensor]] = { + 'nearest': lambda idx, size: torch.clip(idx, 0, size - 1), + 'grid-wrap': lambda idx, size: idx % size, + 'reflect': lambda idx, size: torch.floor_divide(_mirror(2*idx+1, 2*size+1), 2), + 'mirror': _mirror, +} + +_PAD_MODE_MAP: t.Dict[_PadMode, str] = { + 'constant': 'constant', + 'edge': 'replicate', + 'reflect': 'reflect', + 'wrap': 'circular', +} + +def min( + arr: torch.Tensor, axis: t.Union[int, t.Tuple[int, ...], None] = None, *, + keepdims: bool = False +) -> torch.Tensor: + if axis is None: + if keepdims: + return torch.min(arr).reshape((1,) * arr.ndim) + return torch.min(arr) + return torch.amin(arr, axis, keepdim=keepdims) + + +def max( + arr: torch.Tensor, axis: t.Union[int, t.Tuple[int, ...], None] = None, *, + keepdims: bool = False +) -> torch.Tensor: + if axis is None: + if keepdims: + return torch.max(arr).reshape((1,) * arr.ndim) + return torch.max(arr) + return torch.amax(arr, axis, keepdim=keepdims) + + +def nanmin( + arr: torch.Tensor, axis: t.Union[int, t.Tuple[int, ...], None] = None, *, + keepdims: bool = False +) -> torch.Tensor: + return min(torch.nan_to_num(arr, nan=torch.inf), axis, keepdims=keepdims) + + +def nanmax( + arr: torch.Tensor, axis: t.Union[int, t.Tuple[int, ...], None] = None, *, + keepdims: bool = False +) -> torch.Tensor: + return max(torch.nan_to_num(arr, nan=-torch.inf), axis, keepdims=keepdims) + + +def minimum( + x1: ArrayLike, x2: ArrayLike +) -> torch.Tensor: + if not isinstance(x1, torch.Tensor): + x1 = _MockTensor(torch.asarray(x1)) + if not isinstance(x2, torch.Tensor): + x2 = _MockTensor(torch.asarray(x2)) + + return torch.minimum(x1, x2) + + +def maximum( + x1: ArrayLike, x2: ArrayLike +) -> torch.Tensor: + if not isinstance(x1, torch.Tensor): + x1 = _MockTensor(torch.asarray(x1)) + if not isinstance(x2, torch.Tensor): + x2 = _MockTensor(torch.asarray(x2)) + + return torch.maximum(x1, x2) + + +def split( + arr: torch.Tensor, sections: int, *, axis: int = 0 +) -> t.Tuple[torch.Tensor, ...]: + if arr.shape[axis] % sections != 0: + raise ValueError("array split does not result in an equal division") + return torch.split(arr, arr.shape[axis] // sections, axis) + + +def pad( + arr: torch.Tensor, pad_width: t.Union[int, t.Tuple[int, int], t.Sequence[t.Tuple[int, int]]], /, *, + mode: _PadMode = 'constant', cval: float = 0. +) -> torch.Tensor: + if mode not in ('constant', 'edge', 'reflect', 'wrap'): + raise ValueError(f"Unsupported padding mode '{mode}'") + + pad = (pad_width, pad_width) if isinstance(pad_width, int) else pad_width + + if isinstance(pad[0], int): + pad = (pad,) + + if len(pad) == 1: + pad = tuple(pad) * arr.ndim + elif len(pad) != arr.ndim: + raise ValueError(f"Invalid `pad_width` '{pad_width}'.") + + pad = tuple(itertools.chain.from_iterable(t.cast(t.Sequence[t.Tuple[int, int]], reversed(pad)))) + + kwargs = {'value': cval} if mode == 'constant' else {} + return _MockTensor(torch.nn.functional.pad(arr, pad, mode=_PAD_MODE_MAP[mode], **kwargs)) + + +def unwrap(arr: torch.Tensor, discont: t.Optional[float] = None, axis: int = -1, *, + period: float = 2.*numpy.pi) -> torch.Tensor: + if discont is None: + discont = period / 2 + + diff = torch.diff(arr, dim=axis) + dtype = torch.result_type(diff, period) + + if dtype.is_floating_point: + interval_high = period / 2 + boundary_ambiguous = True + else: + interval_high, rem = divmod(period, 2) + boundary_ambiguous = rem == 0 + + interval_low = -interval_high + diffmod = torch.remainder(diff - interval_low, period) + interval_low + if boundary_ambiguous: + diffmod[(diffmod == interval_low) & (diff > 0)] = interval_high + + phase_correct = diffmod - diff + phase_correct[abs(diff) < discont] = 0. + + prepend_shape = list(arr.shape) + prepend_shape[axis] = 1 + return arr + torch.cat([torch.zeros(prepend_shape, dtype=dtype), torch.cumsum(phase_correct, axis)], dim=axis) + + +def indices( + shape: t.Tuple[int, ...], dtype: t.Union[str, None, t.Type[numpy.generic], torch.dtype] = None, sparse: bool = False +) -> t.Union[torch.Tensor, t.Tuple[torch.Tensor, ...]]: + dtype = to_torch_dtype(dtype) if dtype is not None else torch.int64 + + n = len(shape) + + if sparse: + return tuple( + _MockTensor(torch.arange(s, dtype=dtype).reshape((1,) * i + (s,) + (1,) * (n - i - 1))) + for (i, s) in enumerate(shape) + ) + + arrs = tuple(torch.arange(s, dtype=dtype) for s in shape) + return _MockTensor(torch.stack(torch.meshgrid(*arrs, indexing='ij'), dim=0)) + + +def size(arr: torch.Tensor, axis: t.Optional[int]) -> int: + return arr.size(axis) if axis is not None else arr.numel() + + +def asarray( + arr: t.Any, dtype: t.Union[str, torch.dtype, numpy.dtype, t.Type[numpy.generic], None] = None, *, + copy: t.Optional[bool] = None, +) -> _MockTensor: + dtype = to_torch_dtype(dtype) if dtype is not None else None + requires_grad = arr.requires_grad if isinstance(arr, torch.Tensor) else False + + if isinstance(arr, numpy.ndarray) and arr.flags['WRITEABLE'] and not copy: + device = torch.get_default_device() + if device.type == 'cuda': + return _MockTensor(torch.from_numpy(arr).to(device=device, dtype=dtype, non_blocking=True)) + + return _MockTensor(torch.asarray(arr, dtype=dtype, requires_grad=requires_grad, copy=copy)) + + +def affine_transform( + input: torch.Tensor, matrix: ArrayLike, + offset: t.Optional[ArrayLike] = None, + output_shape: t.Optional[t.Tuple[int, ...]] = None, + order: int = 1, mode: _InterpBoundaryMode = 'grid-constant', + cval: ArrayLike = 0.0, +) -> torch.Tensor: + + if output_shape is None: + output_shape = input.shape + n_axes = len(output_shape) # num axes to transform over + + idxs = t.cast(torch.Tensor, indices(output_shape, dtype=torch.float64)) + + matrix = asarray(matrix) + if matrix.size() == (n_axes + 1, n_axes + 1): + # homogenous transform matrix + coords = torch.tensordot( + matrix, torch.stack((*idxs, torch.ones_like(idxs[0])), dim=0), dims=1 + )[:-1] + elif matrix.size() == (n_axes,): + coords = (idxs.T * matrix + asarray(offset)).T + else: + raise ValueError(f"Expected matrix of shape ({n_axes + 1}, {n_axes + 1}) or ({n_axes},), instead got shape {matrix.shape}") + + return _MockTensor(torch.vmap( + lambda a: map_coordinates(a, coords, order=order, mode=mode, cval=cval) + )(input.reshape(-1, *input.shape[-n_axes:])).reshape((*input.shape[:-n_axes], *output_shape))) + + +def map_coordinates( + arr: torch.Tensor, coordinates: torch.Tensor, + order: int = 1, mode: _InterpBoundaryMode = 'grid-constant', + cval: ArrayLike = 0.0 +) -> torch.Tensor: + from phaser.utils.num import to_real_dtype + if arr.ndim != coordinates.shape[0]: + raise ValueError("invalid shape for coordinate array") + + if order not in (0, 1): + raise ValueError(f"Interpolation order {order} not supported (torch currently only supports order=0, 1)") + + if mode == 'grid-constant': + return _map_coordinates_constant( + arr, coordinates, order=order, cval=cval + ) + + remap_fn = _BOUNDARY_FNS.get(mode) + if remap_fn is None: + raise ValueError(f"Interpolation mode '{mode}' not supported (torch supports one of " + "('constant', 'nearest', 'reflect', 'mirror', 'grid-wrap'))") + + weight_dtype = to_torch_dtype(to_real_dtype(to_numpy_dtype(arr.dtype))) + + ax_nodes: t.List[t.Tuple[t.Tuple[torch.Tensor, torch.Tensor], ...]] = [] + + for ax_coords, size in zip(coordinates, arr.shape): + if order == 1: + lower = torch.floor(ax_coords) + upper_weight = ax_coords - lower + lower_idx = lower.type(torch.int32) + ax_nodes.append(( + (remap_fn(lower_idx, size), 1.0 - upper_weight), + (remap_fn(lower_idx + 1, size), upper_weight), + )) + else: + idx = torch.round(ax_coords).type(torch.int32) + ax_nodes.append(((remap_fn(idx, size), torch.ones((), dtype=weight_dtype)),)) + + outputs = [] + for corner in itertools.product(*ax_nodes): + idxs, weights = zip(*corner) + outputs.append(arr[idxs] * functools.reduce(operator.mul, weights)) + + result = functools.reduce(operator.add, outputs) + return _MockTensor(result.type(arr.dtype)) + + +def _map_coordinates_constant( + arr: torch.Tensor, coordinates: torch.Tensor, + order: int = 1, cval: ArrayLike = 0.0 +) -> torch.Tensor: + from phaser.utils.num import to_real_dtype + weight_dtype = to_torch_dtype(to_real_dtype(to_numpy_dtype(arr.dtype))) + cval = torch.tensor(cval) + + is_valid = lambda idx, size: (0 <= idx) & (idx < size) # noqa: E731 + clip = lambda idx, size: torch.clip(idx, 0, size - 1) # noqa: E731 + + ax_nodes: t.List[t.Tuple[t.Tuple[torch.Tensor, torch.Tensor, torch.Tensor], ...]] = [] + + for ax_coords, size in zip(coordinates, arr.shape): + if order == 1: + lower = torch.floor(ax_coords) + upper_weight = ax_coords - lower + lower_idx = lower.type(torch.int32) + ax_nodes.append(( + (clip(lower_idx, size), is_valid(lower_idx, size), 1.0 - upper_weight), + (clip(lower_idx + 1, size), is_valid(lower_idx + 1, size), upper_weight), + )) + else: + idx = torch.round(ax_coords).type(torch.int32) + ax_nodes.append(((clip(idx, size), is_valid(idx, size), torch.ones((), dtype=weight_dtype)),)) + + outputs = [] + for corner in itertools.product(*ax_nodes): + idxs, valids, weights = zip(*corner) + val = torch.where(functools.reduce(operator.and_, valids), arr[idxs], cval) + outputs.append(val * functools.reduce(operator.mul, weights)) + + result = functools.reduce(operator.add, outputs) + return result.type(arr.dtype) + + +def get_devices() -> t.Tuple[torch.device, ...]: + devices = [] + devices.extend(f'cuda:{i}' for i in range(torch.cuda.device_count())) + + if torch.backends.mps.is_available(): + devices.append('mps') + + return tuple(map(torch.device, devices)) + + +def to_device(device: t.Union[str, torch.device]) -> torch.device: + if isinstance(device, torch.device): + return device + return torch.device(device) + + +def set_default_device(device: torch.device): + if not isinstance(device, torch.device): + raise TypeError(f"Invalid device '{device}' for backend torch") + torch.set_default_device(device) + + +def _wrap_call(f, *args: t.Any, **kwargs: t.Any) -> t.Any: + try: + kwargs['dtype'] = to_torch_dtype(kwargs['dtype']) + except KeyError: + pass + + try: + kwargs['dim'] = kwargs.pop('axes') + except KeyError: + try: + kwargs['dim'] = kwargs.pop('axis') + except KeyError: + pass + + if f is torch.asarray and isinstance(args[0], numpy.ndarray): + if not args[0].flags['W']: + raise ValueError() + + result = f(*args, **kwargs) + # TODO: deal with tuples of output, pytrees, etc. here + # this will result in some nasty bugs + if isinstance(result, torch.Tensor): + return _MockTensor(result) + return result + + +mock_torch = _MockModule(torch, { + 'torch.array': functools.update_wrapper(lambda *args, **kwargs: _MockTensor(_wrap_call(torch.asarray, *args, **kwargs)), torch.asarray), # type: ignore + 'torch.asarray': asarray, + 'torch.mod': functools.update_wrapper(lambda *args, **kwargs: _MockTensor(_wrap_call(torch.remainder, *args, **kwargs)), torch.remainder), # type: ignore + 'torch.split': split, + 'torch.pad': pad, + 'torch.min': min, 'torch.max': max, + 'torch.nanmin': nanmin, 'torch.nanmax': nanmax, + 'torch.minimum': minimum, 'torch.maximum': maximum, + 'torch.unwrap': unwrap, + 'torch.indices': indices, + 'torch.size': size, + 'torch.iscomplexobj': lambda arr: torch.is_complex(arr), + 'torch.isrealobj': lambda arr: not torch.is_complex(arr), +}, _wrap_call) + +mock_torch._MockTensor = _MockTensor # type: ignore diff --git a/phaser/utils/image.py b/phaser/utils/image.py index 45c1655..6b92ada 100644 --- a/phaser/utils/image.py +++ b/phaser/utils/image.py @@ -8,7 +8,7 @@ import numpy from numpy.typing import ArrayLike, NDArray -from .num import get_array_module, get_scipy_module, to_numpy, at, is_jax, abs2 +from .num import get_array_module, get_scipy_module, to_numpy, at, abs2, xp_is_jax, xp_is_torch NumT = t.TypeVar('NumT', bound=numpy.number) @@ -131,7 +131,7 @@ def scale_to_integral_type( return (xp.clip((imax + 1) / (vmax - vmin) * (arr - vmin), 0, imax)).astype(dtype) -_BoundaryMode: t.TypeAlias = t.Literal['constant', 'nearest', 'mirror', 'reflect', 'wrap', 'grid-mirror', 'grid-wrap', 'grid-constant'] +_InterpBoundaryMode: t.TypeAlias = t.Literal['constant', 'nearest', 'mirror', 'reflect', 'wrap', 'grid-mirror', 'grid-wrap', 'grid-constant'] def to_affine_matrix(arr: ArrayLike, ndim: int = 2) -> NDArray[numpy.floating]: @@ -182,19 +182,24 @@ def affine_transform( offset: t.Optional[ArrayLike] = None, output_shape: t.Optional[t.Tuple[int, ...]] = None, order: int = 1, - mode: _BoundaryMode = 'grid-constant', + mode: _InterpBoundaryMode = 'grid-constant', cval: t.Union[NumT, float] = 0.0, ) -> NDArray[NumT]: if mode in ('constant', 'wrap'): # these modes aren't supported by jax raise ValueError(f"Resampling mode '{mode}' not supported (try 'grid-constant' or 'grid-wrap' instead)") - xp = get_array_module(input, matrix, offset) - scipy = get_scipy_module(input, matrix, offset) - if is_jax(input): - if order > 1: + if xp_is_torch(xp): + from ._torch_kernels import affine_transform, torch + return t.cast(NDArray[NumT], affine_transform( + t.cast(torch.Tensor, input), matrix, offset, + output_shape, order, mode, cval + )) + + if xp_is_jax(xp): + if order not in (0, 1): raise ValueError(f"Interpolation order {order} not supported (jax currently only supports order=0, 1)") from ._jax_kernels import affine_transform, jax return t.cast(NDArray[NumT], affine_transform( @@ -202,6 +207,8 @@ def affine_transform( output_shape, order, mode, cval )) + scipy = get_scipy_module(input, matrix, offset) + if offset is None: offset = 0. if output_shape is None: diff --git a/phaser/utils/io.py b/phaser/utils/io.py index 700432a..2488099 100644 --- a/phaser/utils/io.py +++ b/phaser/utils/io.py @@ -162,15 +162,30 @@ def hdf5_read_iter_state(group: h5py.Group) -> IterState: ) -def hdf5_read_progress_state(group: h5py.Group) -> ProgressState: - iters = numpy.asarray(_hdf5_read_dataset(group, 'iters', numpy.int64)) - errors = numpy.asarray(_hdf5_read_dataset(group, 'detector_errors', numpy.float64)) - assert iters.ndim == errors.ndim == 1 - assert iters.shape == errors.shape - - return ProgressState( - iters=iters, detector_errors=errors, - ) +def hdf5_read_progress_state(group: h5py.Group) -> t.Dict[str, ProgressState]: + if 'iters' in group and 'detector_errors' in group: + # read old-style, convert to new style + iters = numpy.asarray(_hdf5_read_dataset(group, 'iters', numpy.int64)) + values = numpy.asarray(_hdf5_read_dataset(group, 'detector_errors', numpy.float64)) + assert iters.ndim == values.ndim == 1 + assert iters.shape == values.shape + + return {'total_loss': ProgressState(iters.tolist(), values.tolist())} + + # read new-style + d: t.Dict[str, ProgressState] = {} + + for (k, group) in group.items(): + if not isinstance(group, h5py.Group): + continue + iters = numpy.asarray(_hdf5_read_dataset(group, 'iters', numpy.int64)) + values = numpy.asarray(_hdf5_read_dataset(group, 'values', numpy.float64)) + assert iters.ndim == values.ndim == 1 + assert iters.shape == values.shape + + d[k] = ProgressState(iters.tolist(), values.tolist()) + + return d def hdf5_write_state(state: t.Union[ReconsState, PartialReconsState], file: HdfLike): @@ -208,10 +223,10 @@ def hdf5_write_object_state(state: ObjectState, group: h5py.Group): assert state.data.ndim == 3 assert state.thicknesses.ndim == 1 n_z = state.data.shape[0] - assert state.thicknesses.ndim == 1 - assert state.thicknesses.size == n_z if n_z > 1 else state.thicknesses.size in (0, 1) thick = to_numpy(state.thicknesses) + assert thick.ndim == 1 + assert thick.size == n_z if n_z > 1 else thick.size in (0, 1) group.create_dataset('thicknesses', data=thick) zs = group.create_dataset('zs', data=to_numpy(state.zs())) zs.make_scale("z") @@ -236,12 +251,15 @@ def hdf5_write_iter_state(state: IterState, group: h5py.Group): group.create_dataset("total_iter", (), numpy.uint64, data=state.total_iter) -def hdf5_write_progress_state(state: ProgressState, group: h5py.Group): - iters = group.create_dataset("iters", data=state.iters.astype(numpy.uint64)) - iters.make_scale("total_iter") - dataset = group.create_dataset("detector_errors", data=state.detector_errors.astype(numpy.float64)) - dataset.dims[0].label = 'total_iter' - dataset.dims[0].attach_scale(iters) +def hdf5_write_progress_state(state: t.Dict[str, ProgressState], group: h5py.Group): + for (k, v) in state.items(): + subgroup = group.require_group(k) + + iters = subgroup.create_dataset("iters", data=numpy.array(v.iters, dtype=numpy.int64)) + iters.make_scale("total_iter") + dataset = subgroup.create_dataset("values", data=numpy.array(v.values, dtype=numpy.float64)) + dataset.dims[0].label = 'total_iter' + dataset.dims[0].attach_scale(iters) def _parse_version(version: str) -> t.Tuple[int, ...]: @@ -276,7 +294,7 @@ def _hdf5_read_dataset(group: h5py.Group, path: str, dtype: t.Type[DTypeT]) -> t f"Expected a dataset of dtype '{dtype_category}' at path '{group.name}{path}', instead found {dataset.dtype}.") # ensure promotion is correct. eg dtype = numpy.floating promotes with numpy.float32 - out_dtype = numpy.promote_types(dataset.dtype, _CATEGORY_MIN_DTYPE.get(dtype, dtype)) + out_dtype = numpy.promote_types(dataset.dtype, _CATEGORY_MIN_DTYPE.get(dtype_category, dtype)) return dataset[()].astype(out_dtype) diff --git a/phaser/utils/misc.py b/phaser/utils/misc.py index 0c5467a..cdd118f 100644 --- a/phaser/utils/misc.py +++ b/phaser/utils/misc.py @@ -1,11 +1,11 @@ -import dataclasses +import functools import math +from types import ModuleType import typing as t import numpy from numpy.typing import NDArray from numpy.random import SeedSequence, PCG64, BitGenerator, Generator -from typing_extensions import dataclass_transform T = t.TypeVar('T') @@ -217,79 +217,57 @@ def __eq__(self, other: t.Any) -> bool: round(self, 5) == round(other, 5) -@t.overload -@dataclass_transform(kw_only_default=False, frozen_default=False) -def jax_dataclass(cls: t.Type[T], /, *, - init: bool = True, kw_only: bool = False, frozen: bool = False, - static_fields: t.Sequence[str] = (), drop_fields: t.Sequence[str] = (), -) -> t.Type[T]: - ... - -@t.overload -@dataclass_transform(kw_only_default=False, frozen_default=False) -def jax_dataclass(*, - init: bool = True, kw_only: bool = False, frozen: bool = False, - static_fields: t.Sequence[str] = (), drop_fields: t.Sequence[str] = (), -) -> t.Callable[[t.Type[T]], t.Type[T]]: - ... - -def jax_dataclass(cls: t.Optional[t.Type[T]] = None, /, *, - init: bool = True, kw_only: bool = False, frozen: bool = False, - static_fields: t.Sequence[str] = (), drop_fields: t.Sequence[str] = (), -) -> t.Union[t.Type[T], t.Callable[[t.Type[T]], t.Type[T]]]: - if cls is None: - return lambda cls: jax_dataclass(cls, init=init, kw_only=kw_only, frozen=frozen, - static_fields=static_fields, drop_fields=drop_fields) - - cls = dataclasses.dataclass(init=init, kw_only=kw_only, frozen=frozen)(cls) - _register_dataclass(cls, static_fields=static_fields, drop_fields=drop_fields) - return cls - +def unwrap(val: t.Optional[T]) -> T: + assert val is not None + return val -def _register_dataclass(cls: type, static_fields: t.Sequence[str], drop_fields: t.Sequence[str]): - try: - from jax.tree_util import register_pytree_with_keys - except ImportError: - return - fields = dataclasses.fields(cls) - field_names = {field.name for field in fields} +class _MockModule: + def __init__(self, module: ModuleType, rewrites: t.Dict[str, t.Callable], wrap: t.Callable): + self._inner: ModuleType = module + self._rewrites: t.Dict[str, t.Callable] = rewrites + self._wrap: t.Callable = wrap - if (extra := set(static_fields).difference(field_names)): - raise ValueError(f"Unknown field(s) passed to 'static_fields': {', '.join(map(repr, extra))}") - if (extra := set(drop_fields).difference(field_names)): - raise ValueError(f"Unknown field(s) passed to 'drop_fields': {', '.join(map(repr, extra))}") + self.__name__ = module.__name__ + """ + self.__spec__ = module.__spec__ + self.__package__ = module.__package__ + self.__loader__ = module.__loader__ + self.__path__ = module.__path__ + self.__doc__ = module.__doc__ + self.__annotations__ = module.__annotations__ + if hasattr(module, '__file__') and hasattr(module, '__cached__'): + self.__file__ = module.__file__ + self.__cached__ = module.__cached__ + """ - data_fields = tuple(field_names.difference(static_fields).difference(drop_fields)) + self.__setattr__ = lambda name, val: setattr(self._inner, name, val) - def flatten_with_keys(x: t.Any, /) -> tuple[t.Iterable[tuple[str, t.Any]], t.Hashable]: - meta = tuple(getattr(x, name) for name in static_fields) - trees = tuple((name, getattr(x, name)) for name in data_fields) - return trees, meta + def __getattr__(self, name: t.Any) -> t.Any: + fullpath = f"{self.__name__}.{name}" + if (rewrite := self._rewrites.get(fullpath, None)): + if (val := getattr(self._inner, name, None)) is not None: + return functools.update_wrapper(rewrite, val) + return rewrite - def unflatten(meta: t.Hashable, trees: t.Iterable[t.Any], /) -> t.Any: - if not isinstance(meta, tuple): - raise TypeError - static_args = dict(zip(static_fields, meta, strict=True)) - data_args = dict(zip(data_fields, trees, strict=True)) - return cls(**static_args, **data_args) + val = getattr(self._inner, name) - def flatten(x: t.Any, /) -> tuple[t.Iterable[t.Any], t.Hashable]: - hashed = tuple(getattr(x, name) for name in static_fields) - trees = tuple(getattr(x, name) for name in data_fields) - return trees, hashed + if isinstance(val, ModuleType): + return _MockModule(val, self._rewrites, self._wrap) - register_pytree_with_keys(cls, flatten_with_keys, unflatten, flatten) + if hasattr(val, '__call__') and not isinstance(val, type): + def inner(*args, **kwargs): + return self._wrap(val, *args, **kwargs) + return inner + return functools.update_wrapper(inner, val) -def unwrap(val: t.Optional[T]) -> T: - assert val is not None - return val + return val __all__ = [ 'create_rng', 'create_rng_group', 'create_sparse_groupings', 'create_compact_groupings', 'mask_fraction_of_groups', 'FloatKey', - 'jax_dataclass', 'unwrap', + 'unwrap', ] diff --git a/phaser/utils/num.py b/phaser/utils/num.py index db97103..6e13374 100644 --- a/phaser/utils/num.py +++ b/phaser/utils/num.py @@ -3,21 +3,25 @@ """ import functools +from itertools import chain import logging import warnings +from types import ModuleType, EllipsisType import typing as t +import sys import numpy from numpy.typing import ArrayLike, DTypeLike, NDArray from phaser.types import BackendName -from .misc import jax_dataclass +from .tree import tree_dataclass if t.TYPE_CHECKING: - from phaser.utils.image import _BoundaryMode + from phaser.utils.image import _InterpBoundaryMode +Device: t.TypeAlias = t.Any Float: t.TypeAlias = t.Union[float, numpy.floating] NumT = t.TypeVar('NumT', bound=numpy.number) FloatT = t.TypeVar('FloatT', bound=numpy.floating) @@ -27,98 +31,228 @@ P = t.ParamSpec('P') IndexLike: t.TypeAlias = t.Union[ - int, + int, slice, EllipsisType, NDArray[numpy.integer[t.Any]], NDArray[numpy.bool_], - t.Tuple[t.Union[int, NDArray[numpy.integer[t.Any]], NDArray[numpy.bool_]], ...], + t.Tuple[t.Union[int, slice, EllipsisType, NDArray[numpy.integer[t.Any]], NDArray[numpy.bool_]], ...], ] - logger = logging.getLogger(__name__) -try: + +def _load_cupy() -> ModuleType: + from ._cuda_kernels import mock_cupy + + with warnings.catch_warnings(): + # https://github.com/cupy/cupy/issues/8718 + warnings.filterwarnings(action='ignore', message=r"cupyx\.jit\.rawkernel is experimental", category=FutureWarning) + import cupyx.scipy.signal # pyright: ignore[reportMissingImports,reportUnusedImport] + import cupyx.scipy.ndimage # pyright: ignore[reportMissingImports,reportUnusedImport] # noqa: F401 + + return t.cast(ModuleType, mock_cupy) + +def _load_jax() -> ModuleType: import jax jax.config.update('jax_enable_x64', jax.default_backend() != 'METAL') - #jax.config.update('jax_log_compiles', True) - #jax.config.update('jax_debug_nans', True) -except ImportError: - pass + import jax.scipy + return jax.numpy -def get_backend_module(backend: t.Optional[BackendName] = None): - """Get the module `xp` associated with a compute backend""" - if backend is None: - return get_default_backend_module() +def _load_torch() -> ModuleType: + from ._torch_kernels import mock_torch + return t.cast(ModuleType, mock_torch) - backend = t.cast(BackendName, backend.lower()) - if backend not in ('cuda', 'cupy', 'jax', 'cpu', 'numpy'): - raise ValueError(f"Unknown backend '{backend}'") - if not t.TYPE_CHECKING: +_NAME_REMAP: t.Dict[BackendName, BackendName] = {} + +_LOAD_FNS: t.Dict[BackendName, t.Callable[[], ModuleType]] = { + 'cupy': _load_cupy, + 'jax': _load_jax, + 'torch': _load_torch, +} + + +class _BackendLoader: + def __init__(self): + self.inner: t.Dict[BackendName, t.Optional[ModuleType]] = {} + + def _normalize(self, backend: BackendName) -> BackendName: + name = t.cast(BackendName, backend.lower()) + name = _NAME_REMAP.get(name, name) + + if name not in ('cupy', 'jax', 'numpy', 'torch'): + raise ValueError(f"Unknown backend '{backend}'") + return name + + def _load(self, name: BackendName): try: - if backend == 'jax': - import jax.numpy - return jax.numpy - if backend in ('cupy', 'cuda'): - import cupy - return cupy + self.inner[name] = _LOAD_FNS[name]() except ImportError: - raise ValueError(f"Backend '{backend}' is not available") + self.inner[name] = None - return numpy + def get(self, name: BackendName): + name = self._normalize(name) + if name == 'numpy': + return numpy + if name not in self.inner: + self._load(name) -def detect_supported_backends() -> t.Dict[BackendName, t.Tuple[str, ...]]: - backends: t.Dict[BackendName, t.Tuple[str, ...]] = {'numpy': ('cpu',)} + return None if t.TYPE_CHECKING else self.inner[name] - try: - import jax.numpy # type: ignore - devices = jax.devices() - backends['jax'] = tuple(f"{device.platform}:{device.id}" for device in devices) - except ImportError: - pass + def __getitem__(self, name: BackendName): + if (backend := self.get(name)) is not None: + return backend - try: - import cupy # type: ignore - n_devices = cupy.cuda.runtime.getDeviceCount() - backends['cupy'] = tuple(f'cuda:{i}' for i in range(n_devices)) - except ImportError: - pass + raise ValueError(f"Backend '{name}' is not available") + +_BACKEND_LOADER = _BackendLoader() + + +def get_backend_module(backend: t.Optional[BackendName] = None): + """Get the module `xp` associated with a compute backend""" + if backend is None: + backend = get_default_backend() + + return _BACKEND_LOADER[backend] - return backends +def get_backend_scipy(backend: BackendName): + """Get the scipy module associated with a compute backend""" + + name = _BACKEND_LOADER._normalize(backend) + # ensure backend is loadable + _BACKEND_LOADER[backend] -def get_default_backend_module(): if not t.TYPE_CHECKING: + if name == 'torch': + raise ValueError("`get_backend_scipy` is not supported for the PyTorch backend") + if name == 'jax': + return sys.modules['jax.scipy'] + if name == 'cupy': + return sys.modules['cupyx.scipy'] + + import scipy + return scipy + + +def get_default_backend() -> BackendName: + # check for jax or torch GPUs first + if _BACKEND_LOADER.get('jax') is not None: + import jax try: - import jax.numpy - return jax.numpy - except ImportError: + if len(jax.devices('gpu')): + return 'jax' + except RuntimeError: pass - try: - import cupy - return cupy - except ImportError: + if len(jax.devices('tpu')): + return 'jax' + except RuntimeError: pass - - return numpy + if _BACKEND_LOADER.get('torch') is not None: + import torch + if torch.get_default_device().type != 'cpu': + return 'torch' + + for backend in ('jax', 'torch', 'cupy'): + if _BACKEND_LOADER.get(backend) is not None: + return backend + return 'numpy' + + +def get_devices() -> t.Tuple[t.Tuple[BackendName, Device], ...]: + devices: t.List[t.Tuple[BackendName, Device]] = [] + + if _BACKEND_LOADER.get('jax') is not None: + from ._jax_kernels import get_devices + devices.extend(('jax', device) for device in get_devices()) + if _BACKEND_LOADER.get('torch') is not None: + from ._torch_kernels import get_devices + devices.extend(('torch', device) for device in get_devices()) + if _BACKEND_LOADER.get('cupy') is not None: + from ._cuda_kernels import get_devices + devices.extend(('cupy', device) for device in get_devices()) + devices.append(('numpy', 'cpu')) + + return tuple(devices) + + +def repr_device(device: Device) -> str: + s = str(device) + + return { + 'TFRT_CPU_0': 'cpu', + }.get(s, s) + + +def to_device(device: t.Union[str, Device], xp: t.Any) -> Device: + if xp_is_torch(xp): + from ._torch_kernels import to_device + return to_device(device) + if xp_is_cupy(xp): + from ._cuda_kernels import to_device + return to_device(device) + if xp_is_jax(xp): + from ._jax_kernels import to_device + return to_device(device) + if xp is not numpy: + raise TypeError(f"Expected an array backend, got '{xp}'") + if device != 'cpu': + raise ValueError(f"Invalid device '{device}' for backend 'numpy'") + return device + + +def get_backend_devices(xp: t.Any) -> t.Tuple[Device, ...]: + if xp_is_torch(xp): + from ._torch_kernels import get_devices + return get_devices() + if xp_is_cupy(xp): + from ._cuda_kernels import get_devices + return get_devices() + if xp_is_jax(xp): + from ._jax_kernels import get_devices + return get_devices() + if xp is not numpy: + raise TypeError(f"Expected an array backend, got '{xp}'") + + return ('cpu',) + + +def set_default_device(device: Device, xp: t.Any): + if xp_is_torch(xp): + from ._torch_kernels import set_default_device + set_default_device(device) + elif xp_is_cupy(xp): + from ._cuda_kernels import set_default_device + set_default_device(device) + elif xp_is_jax(xp): + from ._jax_kernels import set_default_device + set_default_device(device) + elif xp is not numpy: + raise TypeError(f"Expected an array backend, got '{xp}'") + elif device != 'cpu': + raise ValueError(f"Invalid device '{device}' for backend 'numpy'") def get_array_module(*arrs: t.Optional[ArrayLike]): - try: - import jax - if any(isinstance(arr, jax.Array) for arr in arrs) \ - and not t.TYPE_CHECKING: - return jax.numpy - except ImportError: - pass - try: - from cupy import get_array_module as f # type: ignore - if not t.TYPE_CHECKING: - return f(*arrs) - except ImportError: - pass + if (xp := _BACKEND_LOADER.get('jax')) is not None: + import jax.tree + if any( + isinstance(arr, xp.ndarray) + for arr in chain.from_iterable(map(jax.tree.leaves, arrs)) + ): + return xp + if (xp := _BACKEND_LOADER.get('torch')) is not None: + from torch.utils._pytree import tree_leaves + if any( + isinstance(arr, (xp._MockTensor, xp._C.TensorBase)) # type: ignore + for arr in chain.from_iterable(map(tree_leaves, arrs)) + ): + return xp + if (xp := _BACKEND_LOADER.get('cupy')) is not None: + if any(isinstance(arr, xp.ndarray) for arr in arrs): + return xp return numpy @@ -131,33 +265,22 @@ def cast_array_module(xp: t.Any): def get_scipy_module(*arrs: t.Optional[ArrayLike]): # pyright: ignore[reportMissingImports,reportUnusedImport] - import scipy - - try: - import jax - if any(isinstance(arr, jax.Array) for arr in arrs) \ - and not t.TYPE_CHECKING: - return jax.scipy - except ImportError: - pass - try: - with warnings.catch_warnings(): - # https://github.com/cupy/cupy/issues/8718 - warnings.filterwarnings(action='ignore', message=r"cupyx\.jit\.rawkernel is experimental", category=FutureWarning) - - import cupyx.scipy.signal # pyright: ignore[reportMissingImports] - import cupyx.scipy.ndimage # pyright: ignore[reportMissingImports] # noqa: F401 - from cupyx.scipy import get_array_module as f # pyright: ignore[reportMissingImports] - - if not t.TYPE_CHECKING: + if not t.TYPE_CHECKING: + if (xp := _BACKEND_LOADER.get('jax')) is not None: + if any(isinstance(arr, xp.ndarray) for arr in arrs): + return sys.modules['jax.scipy'] + if (xp := _BACKEND_LOADER.get('torch')) is not None: + if any(isinstance(arr, (xp._MockTensor, xp._C.TensorBase)) for arr in arrs): # type: ignore + raise ValueError("`get_scipy_module` is not supported for the PyTorch backend") + if (xp := _BACKEND_LOADER.get('cupy')) is not None: + f = sys.modules['cupyx.scipy'].get_array_module return f(*arrs) - except ImportError: - pass + import scipy return scipy -def to_numpy(arr: t.Union[DTypeT, NDArray[DTypeT]], stream=None) -> NDArray[DTypeT]: +def to_numpy(arr: t.Union[DTypeT, NDArray[DTypeT], float, DTypeT], stream=None) -> NDArray[DTypeT]: """ Convert an array to numpy. For cupy backend, this is equivalent to `cupy.asnumpy`. @@ -165,7 +288,8 @@ def to_numpy(arr: t.Union[DTypeT, NDArray[DTypeT]], stream=None) -> NDArray[DTyp if not t.TYPE_CHECKING: if is_jax(arr): return numpy.array(arr) - + if is_torch(arr): + return arr.numpy(force=True) if is_cupy(arr): return arr.get(stream) @@ -180,7 +304,8 @@ def as_numpy(arr: ArrayLike, stream=None) -> NDArray: if not t.TYPE_CHECKING: if is_jax(arr): return numpy.array(arr) - + if is_torch(arr): + return arr.numpy(force=True) if is_cupy(arr): return arr.get(stream) @@ -201,46 +326,57 @@ def as_array(arr: ArrayLike, xp: t.Any = None) -> numpy.ndarray: return numpy.asarray(arr) -def is_cupy(arr: NDArray[DTypeT]) -> bool: - try: - import cupy # pyright: ignore[reportMissingImports] - except ImportError: +def is_cupy(arr: NDArray[numpy.generic]) -> bool: + if (cupy := _BACKEND_LOADER.get('cupy')) is None: return False return isinstance(arr, cupy.ndarray) def is_jax(arr: t.Any) -> bool: - try: - import jax # pyright: ignore[reportMissingImports] - except ImportError: + if (jnp := _BACKEND_LOADER.get('jax')) is None: return False + import jax # pyright[ignoreMissingImports] + return any( - isinstance(arr, jax.Array) for arr in jax.tree_util.tree_leaves(arr) + isinstance(arr, jnp.ndarray) + for arr in jax.tree_util.tree_leaves(arr) ) -def xp_is_cupy(xp: t.Any) -> bool: - try: - import cupy # pyright: ignore[reportMissingImports] - return xp is cupy - except ImportError: +def is_torch(arr: t.Any) -> bool: + if (torch := t.cast(ModuleType, _BACKEND_LOADER.get('torch'))) is None: return False + return any( + isinstance(arr, (torch._MockTensor, torch._C.TensorBase)) + for arr in torch.utils._pytree.tree_leaves(arr) + ) + + +def xp_is_cupy(xp: t.Any) -> bool: + return xp is sys.modules.get('cupy') def xp_is_jax(xp: t.Any) -> bool: - try: - import jax.numpy # pyright: ignore[reportMissingImports] - return xp is jax.numpy - except ImportError: + return xp is sys.modules.get('jax.numpy') + +def xp_is_torch(xp: t.Any) -> bool: + if (torch := _BACKEND_LOADER.get('torch')) is None: return False + return xp is torch def block_until_ready(arr: NDArray[DTypeT]) -> NDArray[DTypeT]: if hasattr(arr, 'block_until_ready'): # jax return arr.block_until_ready() # type: ignore + if is_torch(arr): + import torch + device = torch.get_default_device() + if device.type == 'cuda': + torch.cuda.synchronize(device) + if is_cupy(arr): - import cupy # pyright: ignore[reportMissingImports] + cupy = sys.modules['cupy'] stream = cupy.cuda.get_current_stream() stream.synchronize() @@ -261,20 +397,17 @@ def __init__( self.inner = f functools.update_wrapper(self, f) - if cupy_fuse: - try: - import cupy # pyright: ignore[reportMissingImports] - self.inner = cupy.fuse()(self.inner) - except ImportError: - pass + if cupy_fuse and (cupy := _BACKEND_LOADER.get('cupy')): + self.inner = cupy.fuse()(self.inner) # type: ignore # in jax: self.__call__ -> jax.jit -> jax_f -> f # otherwise: self.__call__ -> f - try: - import jax - except ImportError: - self.jax_jit = None - else: + if _BACKEND_LOADER.get('jax') is not None: + if t.TYPE_CHECKING: + import jax + else: + jax = sys.modules['jax'] + @functools.wraps(f) def jax_f(*args: P.args, **kwargs: P.kwargs) -> T: logger.info(f"JIT-compiling kernel '{self.__qualname__}'...") @@ -285,6 +418,8 @@ def jax_f(*args: P.args, **kwargs: P.kwargs) -> T: donate_argnums=donate_argnums, donate_argnames=donate_argnames, inline=inline, #compiler_options=compiler_options ) + else: + self.jax_jit = None def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: @@ -316,12 +451,8 @@ def fuse(*args, **kwargs) -> t.Callable[[T], T]: """ Equivalent to `cupy.fuse`, if supported. """ - try: - import cupy # pyright: ignore[reportMissingImports] - if not t.TYPE_CHECKING: - return cupy.fuse(*args, **kwargs) - except ImportError: - pass + if (xp := _BACKEND_LOADER.get('cupy')): + return xp.fuse(*args, **kwargs) # type: ignore return lambda x: x @@ -333,6 +464,17 @@ def debug_callback(callback: t.Callable[P, None], *args: P.args, **kwargs: P.kwa callback(*args, **kwargs) +def assert_dtype(arr: numpy.ndarray, dtype: t.Type[numpy.generic]): + if is_torch(arr): + from ._torch_kernels import to_torch_dtype, to_numpy_dtype + + if arr.dtype != to_torch_dtype(dtype): + raise TypeError(f"Expected array to be dtype {dtype}, got dtype {to_numpy_dtype(arr.dtype)} instead") + else: + if arr.dtype != dtype: + raise TypeError(f"Expected array to be dtype {dtype}, got dtype {arr.dtype} instead") + + _COMPLEX_MAP: t.Dict[t.Type[numpy.floating], t.Type[numpy.complexfloating]] = { numpy.floating: numpy.complexfloating, numpy.float32: numpy.complex64, @@ -367,6 +509,9 @@ def to_complex_dtype(dtype: DTypeLike) -> t.Type[numpy.complexfloating]: """ Convert a floating point dtype to a complex version. """ + if _BACKEND_LOADER.get('torch') is not None: + from ._torch_kernels import to_numpy_dtype + dtype = to_numpy_dtype(dtype) # type: ignore if not (isinstance(dtype, type) and issubclass(dtype, numpy.generic)): dtype = numpy.dtype(dtype).type @@ -399,6 +544,9 @@ def to_real_dtype(dtype: DTypeLike) -> t.Type[numpy.floating]: """ Convert a complex dtype to a plain float version. """ + if _BACKEND_LOADER.get('torch') is not None: + from ._torch_kernels import to_numpy_dtype + dtype = to_numpy_dtype(dtype) # type: ignore if not (isinstance(dtype, type) and issubclass(dtype, numpy.generic)): dtype = numpy.dtype(dtype).type @@ -439,6 +587,8 @@ def ifft2(a: ArrayLike) -> NDArray[numpy.complexfloating]: """ xp = get_array_module(a) + if xp_is_torch(xp): + return xp.fft.fftshift(xp.fft.ifft2(a, norm='ortho'), dim=(-2, -1)) # type: ignore return xp.fft.fftshift(xp.fft.ifft2(a, norm='ortho'), axes=(-2, -1)) @t.overload @@ -465,6 +615,8 @@ def fft2(a: ArrayLike) -> NDArray[numpy.complexfloating]: """ xp = get_array_module(a) + if xp_is_torch(xp): + return xp.fft.fft2(xp.fft.ifftshift(a, dim=(-2, -1)), norm='ortho') # type: ignore return xp.fft.fft2(xp.fft.ifftshift(a, axes=(-2, -1)), norm='ortho') @@ -506,10 +658,51 @@ def abs2(x: ArrayLike) -> NDArray[numpy.floating]: """ Return the squared amplitude of a complex array. - This is cheaper than `abs(x)**2.` + This is cheaper than `abs(x)**2` """ - x = get_array_module(x).array(x) - return x.real**2. + x.imag**2. # type: ignore + xp = get_array_module(x) + x = xp.asarray(x) + + if xp_is_torch(xp): + if not xp.is_complex(x): # type: ignore + return x**2 # type: ignore + else: + if not xp.iscomplexobj(x): + return x**2 # type: ignore + + return x.real**2 + x.imag**2 # type: ignore + + +_PadMode: t.TypeAlias = t.Literal['constant', 'edge', 'reflect', 'wrap'] + + +@t.overload +def pad( + arr: NDArray[DTypeT], pad_width: t.Union[int, t.Tuple[int, int], t.Sequence[t.Tuple[int, int]]], /, *, + mode: _PadMode = 'constant', cval: float = 0., +) -> NDArray[DTypeT]: + ... + +@t.overload +def pad( + arr: ArrayLike, pad_width: t.Union[int, t.Tuple[int, int], t.Sequence[t.Tuple[int, int]]], /, *, + mode: _PadMode = 'constant', cval: float = 0., +) -> numpy.ndarray: + ... + +def pad( + arr: ArrayLike, pad_width: t.Union[int, t.Tuple[int, int], t.Sequence[t.Tuple[int, int]]], /, *, + mode: _PadMode = 'constant', cval: float = 0., +) -> numpy.ndarray: + xp = get_array_module(arr) + + if xp_is_torch(xp): + pass + #from ._torch_kernels import pad + #return pad(arr, pad_width, mode=mode, cval=cval) # type: ignore + + return xp.pad(arr, pad_width, mode=mode, constant_values=cval) + @t.overload @@ -529,6 +722,9 @@ def ufunc_outer(ufunc: numpy.ufunc, x: ArrayLike, y: ArrayLike) -> numpy.ndarray from ._jax_kernels import outer return outer(ufunc, x, y) + if not t.TYPE_CHECKING and is_torch(x): + return ufunc(x[(..., *((None,) * y.ndim))], y[(*((None,) * x.ndim), ...)]) + return ufunc.outer(x, y) @@ -541,7 +737,7 @@ def check_finite(*arrs: NDArray[numpy.inexact], context: t.Optional[str] = None) raise ValueError("NaN or inf encountered") -@jax_dataclass(frozen=True, init=False, drop_fields=('extent',)) +@tree_dataclass(frozen=True, init=False, drop_fields=('extent',)) class Sampling: shape: NDArray[numpy.int_] """Sampling shape (n_y, n_x)""" @@ -714,7 +910,7 @@ def resample( self, arr: NDArray[NumT], new_samp: 'Sampling', *, rotation: float = 0.0, order: int = 1, - mode: '_BoundaryMode' = 'grid-constant', + mode: '_InterpBoundaryMode' = 'grid-constant', cval: t.Union[NumT, float] = 0.0, ) -> NDArray[NumT]: from .image import affine_transform, rotation_matrix @@ -736,7 +932,7 @@ def resample_recip( self, arr: NDArray[NumT], new_samp: 'Sampling', *, rotation: float = 0.0, order: int = 1, - mode: '_BoundaryMode' = 'grid-constant', + mode: '_InterpBoundaryMode' = 'grid-constant', cval: t.Union[NumT, float] = 0.0, fftshift: bool = True, ) -> NDArray[NumT]: @@ -811,7 +1007,8 @@ def at(arr: NDArray[DTypeT], idx: IndexLike) -> _AtImpl[DTypeT]: __all__ = [ - 'get_backend_module', 'get_default_backend_module', + 'get_backend_module', 'get_default_backend', + 'get_devices', 'repr_device', 'to_device', 'get_backend_devices', 'set_default_device', 'get_array_module', 'cast_array_module', 'get_scipy_module', 'to_numpy', 'as_numpy', 'as_array', 'is_cupy', 'is_jax', 'xp_is_cupy', 'xp_is_jax', diff --git a/phaser/utils/object.py b/phaser/utils/object.py index cadb136..c211523 100644 --- a/phaser/utils/object.py +++ b/phaser/utils/object.py @@ -12,13 +12,14 @@ from numpy.typing import ArrayLike, DTypeLike, NDArray from typing_extensions import Self -from .num import get_array_module, cast_array_module, to_real_dtype, as_numpy, at +from .num import get_array_module, cast_array_module, is_torch, to_real_dtype, as_numpy, at from .num import as_array, is_cupy, is_jax, NumT, ComplexT, DTypeT -from .misc import create_rng, jax_dataclass +from .tree import tree_dataclass +from .misc import create_rng if t.TYPE_CHECKING: - from phaser.utils.image import _BoundaryMode + from phaser.utils.image import _InterpBoundaryMode @t.overload @@ -49,7 +50,7 @@ def random_phase_object(shape: t.Iterable[int], sigma: float = 1e-6, *, seed: t. rng = create_rng(seed, 'random_phase_object') real_dtype = to_real_dtype(dtype) if dtype is not None else numpy.float64 - obj_angle = xp2.array(rng.normal(0., sigma, tuple(shape)), dtype=real_dtype) + obj_angle = xp2.asarray(rng.normal(0., sigma, tuple(shape)), dtype=real_dtype) return xp2.cos(obj_angle) + xp2.sin(obj_angle) * 1.j @@ -98,7 +99,7 @@ def resample_slices( # TODO more options in this case? new_total_thick = numpy.sum(new_thicknesses) - slice_frac = xp.array((new_thicknesses / new_total_thick)[(slice(None), *repeat(None, obj.ndim - 1))]) + slice_frac = xp.asarray((new_thicknesses / new_total_thick)[(slice(None), *repeat(None, obj.ndim - 1))]) return xp.exp((xp.log(obj) * slice_frac).astype(obj.dtype)) if obj.shape[0] != len(old_thicknesses): @@ -178,7 +179,7 @@ def _interp1d(arr: NDArray[NumT], old_zs: NDArray[numpy.floating], new_zs: NDArr else: slice_i = slice_is[i] # linearly interpolate - t = xp.array(float((new_z - old_zs[slice_i]) / delta_zs[slice_i]), dtype=real_dtype) + t = xp.asarray(float((new_z - old_zs[slice_i]) / delta_zs[slice_i]), dtype=real_dtype) slice = ((1-t)*arr[slice_i] + t*arr[slice_i + 1]).astype(arr.dtype) new_arr = at(new_arr, i).set(slice) @@ -186,7 +187,7 @@ def _interp1d(arr: NDArray[NumT], old_zs: NDArray[numpy.floating], new_zs: NDArr return new_arr -@jax_dataclass(frozen=True, init=False) +@tree_dataclass(frozen=True, init=False) class ObjectSampling: shape: NDArray[numpy.int_] """Sampling shape `(n_y, n_x)`""" @@ -286,19 +287,17 @@ def check_scan(self, scan_positions: NDArray[numpy.floating], pad: ArrayLike = 0 (scan_positions[..., 1] < obj_min[1]) | (scan_positions[..., 1] > obj_max[1]) ) if (n_outside := int(xp.sum(outside))): - raise ValueError(f"{n_outside}/{outside.size} probe positions completely outside object") + raise ValueError(f"{n_outside}/{xp.size(outside)} probe positions completely outside object") def _pos_to_object_idx(self, pos: ArrayLike, cutout_shape: t.Tuple[int, ...]) -> NDArray[numpy.float64]: """Return starting index for the cutout closest to centered around `pos` (`(y, x)`)""" - - if not is_jax(pos): # allow jax tracers to work right - pos = as_numpy(pos) + xp = get_array_module(pos) # for a given cutout, shift to the top left pixel of that cutout # e.g. a 2x2 cutout needs shifted by s/2 - shift = -numpy.maximum(0., (numpy.array(cutout_shape[-2:]) - 1.)) / 2. + shift = -xp.maximum(0., (xp.array(cutout_shape[-2:]) - 1.)) / 2. - return ((pos - self.corner) / self.sampling + shift).astype(numpy.float64) # type: ignore + return ((pos - xp.array(self.corner.copy())) / xp.array(self.sampling.copy()) + shift).astype(numpy.float64) # type: ignore def slice_at_pos(self, pos: ArrayLike, cutout_shape: t.Tuple[int, ...]) -> t.Tuple[slice, slice]: """ @@ -312,9 +311,10 @@ def slice_at_pos(self, pos: ArrayLike, cutout_shape: t.Tuple[int, ...]) -> t.Tup Returns slices which can be used to index into an object. E.g. `obj[slice_at_pos(pos, (32, 32))]` will return an array of shape `(32, 32)`. """ + xp = get_array_module(pos) idxs = self._pos_to_object_idx(pos, cutout_shape) - (start_i, start_j) = map(int, numpy.round(idxs).astype(numpy.int64)) + (start_i, start_j) = map(int, xp.round(idxs).astype(numpy.int64)) assert start_i >= 0 and start_j >= 0 return ( slice(start_i, start_i + cutout_shape[-2]), @@ -327,8 +327,10 @@ def get_subpx_shifts(self, pos: ArrayLike, cutout_shape: t.Tuple[int, ...]) -> N Returns the shift from the rounded position towards the actual position, in length units. """ + xp = get_array_module(pos) + pos = self._pos_to_object_idx(as_array(pos), cutout_shape) - return (pos - get_array_module(pos).round(pos)).astype(numpy.float64) * self.sampling + return (pos - xp.round(pos)).astype(numpy.float64) * xp.asarray(self.sampling, copy=True) @t.overload def cutout( # pyright: ignore[reportOverlappingOverload] @@ -342,7 +344,7 @@ def cutout(self, arr: numpy.ndarray, pos: ArrayLike, shape: t.Tuple[int, ...]) - def cutout(self, arr: numpy.ndarray, pos: ArrayLike, shape: t.Tuple[int, ...]) -> ObjectCutout[t.Any]: xp = get_array_module(arr, pos) - return ObjectCutout(self, xp.array(arr), xp.array(pos), shape) + return ObjectCutout(self, xp.asarray(arr), xp.asarray(pos), shape) def get_view_at_pos(self, arr: NDArray[NumT], pos: ArrayLike, shape: t.Tuple[int, ...]) -> NDArray[NumT]: """ @@ -379,8 +381,8 @@ def get_region_crop(self, pad: ArrayLike = 0.) -> t.Tuple[slice, slice]: def get_region_mask(self, pad: ArrayLike = 0., *, xp: t.Any = None) -> NDArray[numpy.bool_]: xp2 = numpy if xp is None else cast_array_module(xp) - mask = xp2.zeros(self.shape, dtype=numpy.bool_) - mask = at(mask, self.get_region_crop(pad=pad)).set(numpy.bool_(1)) # type: ignore + mask = xp2.zeros(tuple(self.shape), dtype=numpy.bool_) + mask = at(mask, self.get_region_crop(pad=pad)).set(t.cast(numpy.bool_, 1)) return mask def get_region_center(self) -> NDArray[numpy.floating]: @@ -429,7 +431,7 @@ def mpl_extent(self, center: bool = True) -> t.Tuple[float, float, float, float] def resample( self, arr: NDArray[NumT], new_samp: 'ObjectSampling', *, - order: int = 1, mode: '_BoundaryMode' = 'grid-constant', + order: int = 1, mode: '_InterpBoundaryMode' = 'grid-constant', cval: t.Union[NumT, float] = 1.0, rotation: t.Optional[float] = None, affine: t.Optional[ArrayLike] = None, @@ -491,8 +493,8 @@ class ObjectCutout(t.Generic[DTypeT]): _start_idxs: NDArray[numpy.int_] = field(init=False) def __post_init__(self): - self._start_idxs = numpy.round(self.sampling._pos_to_object_idx(self.pos, self.cutout_shape)).astype(numpy.int_) # type: ignore - self._start_idxs = get_array_module(self.obj).array(self._start_idxs) + xp = get_array_module(self.pos) + self._start_idxs = xp.round(self.sampling._pos_to_object_idx(self.pos, self.cutout_shape)).astype(numpy.int_) # type: ignore @property def shape(self) -> t.Tuple[int, ...]: @@ -503,6 +505,10 @@ def get(self) -> NDArray[DTypeT]: from ._jax_kernels import get_cutouts return t.cast(NDArray[DTypeT], get_cutouts(self.obj, self._start_idxs, tuple(self.cutout_shape))) + if is_torch(self.obj): + from ._torch_kernels import get_cutouts + return get_cutouts(self.obj, self._start_idxs, tuple(self.cutout_shape)) # type: ignore + if is_cupy(self.obj): try: from ._cuda_kernels import get_cutouts diff --git a/phaser/utils/optics.py b/phaser/utils/optics.py index e027b29..cdb406c 100644 --- a/phaser/utils/optics.py +++ b/phaser/utils/optics.py @@ -165,7 +165,7 @@ def fourier_shift_filter(ky: NDArray[numpy.floating], kx: NDArray[numpy.floating xp = get_array_module(ky, kx) dtype = to_complex_dtype(ky.dtype) - (y, x) = split_array(xp.array(shifts, dtype=ky.dtype), axis=-1) + (y, x) = split_array(xp.asarray(shifts, dtype=ky.dtype), axis=-1) return xp.exp(xp.array(-2.j*numpy.pi, dtype=dtype) * (ufunc_outer(xp.multiply, x, kx) + ufunc_outer(xp.multiply, y, ky))) diff --git a/phaser/utils/scan.py b/phaser/utils/scan.py index 26d26e8..1ee5bbb 100644 --- a/phaser/utils/scan.py +++ b/phaser/utils/scan.py @@ -12,16 +12,16 @@ @t.overload def make_raster_scan(shape: t.Tuple[int, int], scan_step: ArrayLike, # pyright: ignore[reportOverlappingOverload] - rotation: float = 0., *, dtype: NumT, xp: t.Any = None) -> NDArray[NumT]: + rotation: float = 0., affine: t.Union[None, ArrayLike] = None, *, dtype: NumT, xp: t.Any = None) -> NDArray[NumT]: ... @t.overload def make_raster_scan(shape: t.Tuple[int, int], scan_step: ArrayLike, - rotation: float = 0., *, dtype: t.Optional[DTypeLike] = None, xp: t.Any = None) -> NDArray[numpy.floating]: + rotation: float = 0., affine: t.Union[None, ArrayLike] = None, *, dtype: t.Optional[DTypeLike] = None, xp: t.Any = None) -> NDArray[numpy.floating]: ... def make_raster_scan(shape: t.Tuple[int, int], scan_step: ArrayLike, - rotation: float = 0., *, dtype: t.Any = None, xp: t.Any = None) -> NDArray[numpy.number]: + rotation: float = 0., affine: t.Union[None, ArrayLike] = None, *, dtype: t.Any = None, xp: t.Any = None) -> NDArray[numpy.number]: """ Make a raster scan, centered around the origin. @@ -42,17 +42,21 @@ def make_raster_scan(shape: t.Tuple[int, int], scan_step: ArrayLike, dtype = numpy.float64 # TODO actually center this around (0, 0) - yy = xp2.arange(shape[0], dtype=dtype) - xp2.array(shape[0] / 2., dtype=dtype) - xx = xp2.arange(shape[1], dtype=dtype) - xp2.array(shape[1] / 2., dtype=dtype) + yy = xp2.arange(shape[0], dtype=dtype) - xp2.asarray(shape[0] / 2., dtype=dtype) + xx = xp2.arange(shape[1], dtype=dtype) - xp2.asarray(shape[1] / 2., dtype=dtype) pts = xp2.stack(xp2.meshgrid(yy, xx, indexing='ij'), axis=-1) + pts *= xp2.broadcast_to(xp2.asarray(scan_step), (2,)).astype(dtype) + if affine is not None: + affine = xp2.asarray(affine, dtype=dtype) + pts = (pts @ affine.T) if rotation != 0.: theta = rotation * numpy.pi/180. - mat = xp2.array([[numpy.cos(theta), -numpy.sin(theta)], [numpy.sin(theta), numpy.cos(theta)]], dtype=dtype) + mat = xp2.asarray([[numpy.cos(theta), -numpy.sin(theta)], [numpy.sin(theta), numpy.cos(theta)]], dtype=dtype) pts = (pts @ mat.T) - return pts * xp2.broadcast_to(xp2.array(scan_step), (2,)).astype(dtype) # type: ignore + return t.cast(NDArray[numpy.number], pts) __all__ = [ diff --git a/phaser/utils/tree.py b/phaser/utils/tree.py new file mode 100644 index 0000000..b29fd07 --- /dev/null +++ b/phaser/utils/tree.py @@ -0,0 +1,448 @@ +import dataclasses +import functools +import typing as t + +import numpy +from numpy.typing import ArrayLike, DTypeLike, NDArray +from typing_extensions import Self, dataclass_transform + +T = t.TypeVar('T') +Leaf: t.TypeAlias = t.Any +Tree: t.TypeAlias = t.Any +field = dataclasses.field + +class TreeSpec(t.Protocol): + @property + def num_leaves(self) -> int: + ... + + @property + def num_nodes(self) -> int: + ... + + def unflatten(self, leaves: t.Iterable[Leaf], /) -> Tree: + ... + + def flatten_up_to(self, xs: Tree, /) -> t.List[Tree]: + ... + + def __eq__(self, other: Self, /) -> bool: # pyright: ignore[reportIncompatibleMethodOverride] + ... + + def __ne__(self, other: Self, /) -> bool: # pyright: ignore[reportIncompatibleMethodOverride] + ... + +class Key(t.Protocol): + def __hash__(self) -> int: + ... + + def __eq__(self, other: object) -> bool: + ... + + def __str__(self) -> str: + ... + +class GetAttrKey(Key, t.Protocol): + @property + def name(self) -> str: + ... + + +KeyPath: t.TypeAlias = t.Tuple[Key, ...] + + +def flatten( + tree: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> t.Tuple[t.List[Leaf], TreeSpec]: + from phaser.utils.num import is_torch + + if is_torch(tree): + from torch.utils._pytree import tree_flatten # type: ignore + return tree_flatten(tree, is_leaf) + + import jax.tree # type: ignore + return jax.tree.flatten(tree, is_leaf) + + +def flatten_with_path( + tree: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> t.Tuple[t.List[t.Tuple[KeyPath, Leaf]], TreeSpec]: + from phaser.utils.num import is_torch + + if is_torch(tree): + from torch.utils._pytree import tree_flatten_with_path # type: ignore + return tree_flatten_with_path(tree, is_leaf) # type: ignore + + from jax.tree_util import tree_flatten_with_path + return tree_flatten_with_path(tree, is_leaf) + + +def unflatten( + leaves: t.Iterable[t.Any], + treespec: TreeSpec +) -> Tree: + try: + from torch.utils._pytree import TreeSpec + if isinstance(treespec, TreeSpec): + return treespec.unflatten(leaves) + except ImportError: + pass + try: + from jax.tree_util import PyTreeDef + if isinstance(treespec, PyTreeDef): + return treespec.unflatten(leaves) + except ImportError: + pass + + raise TypeError( + f"tree_unflatten expected `treespec` to be a TreeSpec, " + f"got item of type {type(treespec)} instead." + ) + + +def map( + f: t.Callable[..., t.Any], + tree: Tree, + *rest: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> t.Any: + from phaser.utils.num import is_torch + + if is_torch(tree): + from torch.utils._pytree import tree_map # type: ignore + return tree_map(f, tree, *rest, is_leaf=is_leaf) + + import jax.tree # type: ignore + return jax.tree.map(f, tree, *rest, is_leaf=is_leaf) + + +def reduce( + f: t.Callable[[T, t.Any], T], tree: Tree, initializer: T, *, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> T: + return functools.reduce(f, leaves(tree, is_leaf=is_leaf), initializer) + + +def sum(tree: Tree) -> numpy.ndarray: + from phaser.utils.num import get_array_module + + xp = get_array_module(tree) + sums = map(xp.sum, tree) + return reduce(lambda lhs, rhs: lhs + rhs, sums, initializer=0) + + +def map_with_path( + f: t.Callable[..., t.Any], + tree: Tree, + *rest: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> t.Any: + from phaser.utils.num import is_torch + + if is_torch(tree): + from torch.utils._pytree import tree_map_with_path # type: ignore + + def wrapper(path: KeyPath, *leaves: t.Any): + return f(tuple(path), *leaves) + + return tree_map_with_path(wrapper, tree, *rest, is_leaf=is_leaf) + + from jax.tree_util import tree_map_with_path # type: ignore + return tree_map_with_path(f, tree, *rest, is_leaf=is_leaf) + + +def grad( + f: t.Callable, + argnums: t.Union[int, t.Tuple[int, ...]] = 0, + has_aux: bool = False, *, xp: t.Optional[t.Any] = None, + sign: float = 1.0, +) -> t.Callable[..., Tree]: + from phaser.utils.num import xp_is_torch, xp_is_jax + + if xp is None or xp_is_jax(xp): + import jax # type: ignore + f = jax.grad(f, argnums, has_aux=has_aux) + # conjugate to get Wirtinger derivative (not required on torch) + conj = True + elif xp_is_torch(xp): + import torch.func # type: ignore + f = torch.func.grad(f, argnums, has_aux=has_aux) + # torch conjugates automatically + conj = False + else: + raise ValueError("`grad` is only supported for backends 'jax' and 'torch'") + + @functools.wraps(f) + def wrapper(*args: t.Any, **kwargs: t.Any) -> Tree: + if has_aux: + (grad, aux) = f(*args, **kwargs) + else: + aux = None + grad = f(*args, **kwargs) + + if conj: + grad = map(lambda arr: arr.conj() * sign, grad, is_leaf=lambda x: x is None) + else: + grad = map(lambda arr: arr * sign, grad, is_leaf=lambda x: x is None) + + return (grad, aux) if has_aux else grad + + return wrapper + + +def value_and_grad( + f: t.Callable, + argnums: t.Union[int, t.Tuple[int, ...]] = 0, + has_aux: bool = False, *, xp: t.Optional[t.Any] = None, + sign: float = 1.0, +) -> t.Callable[..., t.Tuple[Tree, Tree]]: + from phaser.utils.num import xp_is_torch, xp_is_jax + + if xp is None or xp_is_jax(xp): + import jax # type: ignore + f = jax.value_and_grad(f, argnums, has_aux=has_aux) + + @functools.wraps(f) + def jax_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Tuple[Tree, Tree]: + (value, grad) = f(*args, **kwargs) + # conjugate to get Wirtinger derivative, multiply by sign + grad = map(lambda arr: arr.conj() * sign, grad, is_leaf=lambda x: x is None) + return (value, grad) + + return jax_wrapper + + if not xp_is_torch(xp): + raise ValueError("`value_and_grad` is only supported for backends 'jax' and 'torch'") + + import torch.func # type: ignore + f = torch.func.grad_and_value(f, argnums, has_aux=has_aux) + + @functools.wraps(f) + def torch_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Tuple[Tree, Tree]: + # flip order of return values + (grad, value) = f(*args, **kwargs) + # multiply by sign + grad = map(lambda arr: arr * sign, grad, is_leaf=lambda x: x is None) + return (value, grad) + + return torch_wrapper + + +def leaves( + tree: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> t.List[Leaf]: + return flatten(tree, is_leaf)[0] + + +def structure( + tree: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> TreeSpec: + return flatten(tree, is_leaf)[1] + + +def leaves_with_path( + tree: Tree, + is_leaf: t.Optional[t.Callable[..., t.Any]] = None, +) -> t.List[t.Tuple[KeyPath, Leaf]]: + return flatten_with_path(tree, is_leaf)[0] + + +def zeros_like( + tree: Tree, dtype: DTypeLike = None, +) -> Tree: + from phaser.utils.num import get_array_module + xp = get_array_module(tree) + kwargs: t.Dict[str, t.Any] = {'dtype': dtype} if dtype is not None else {} + return map(lambda x: xp.zeros_like(x, **kwargs), tree) + + +def ones_like( + tree: Tree, dtype: DTypeLike = None, +) -> Tree: + from phaser.utils.num import get_array_module + xp = get_array_module(tree) + kwargs: t.Dict[str, t.Any] = {'dtype': dtype} if dtype is not None else {} + return map(lambda x: xp.ones_like(x, **kwargs), tree) + + +def full_like( + tree: Tree, fill_value: ArrayLike, + dtype: DTypeLike = None, +) -> Tree: + from phaser.utils.num import get_array_module + xp = get_array_module(tree) + kwargs: t.Dict[str, t.Any] = {'dtype': dtype} if dtype is not None else {} + return map(lambda x: xp.full_like(x, fill_value, **kwargs), tree) + + +def cast( + tree: Tree, dtype: t.Optional[DTypeLike], +) -> Tree: + if dtype is None: + return tree + return map(lambda x: x.astype(dtype), tree) + + +def clip( + tree: Tree, + min_value: t.Optional[ArrayLike] = None, + max_value: t.Optional[ArrayLike] = None, +) -> Tree: + from phaser.utils.num import get_array_module + xp = get_array_module(tree) + return map(lambda x: xp.clip(x, min_value, max_value), tree) + + +def conj( + tree: Tree +) -> Tree: + from phaser.utils.num import get_array_module + xp = get_array_module(tree) + return map(xp.conj, tree) + + +def update_moment(updates: Tree, moments: Tree, decay: float, order: int) -> Tree: + return map( + lambda g, t: ( + (1 - decay) * (g**order) + decay * t if g is not None else None + ), + updates, + moments, + is_leaf=lambda x: x is None, + ) + + +def update_moment_per_elem_norm(updates: Tree, moments: Tree, decay: float, order: int) -> Tree: + from phaser.utils.num import get_array_module, abs2 + xp = get_array_module(updates, moments) + + def orderth_norm(g): + if xp.isrealobj(g): + return g ** order + + half_order = order / 2 + # JAX generates different HLO for int and float `order` + if half_order.is_integer(): + half_order = int(half_order) + return abs2(g) ** half_order + + return map( + lambda g, t: ( + (1 - decay) * orderth_norm(g) + decay * t if g is not None else None + ), + updates, + moments, + is_leaf=lambda x: x is None, + ) + + +def bias_correction(moment: Tree, decay: float, count: t.Union[int, NDArray[numpy.integer]]) -> Tree: + bias_correction = t.cast(NDArray[numpy.floating], 1 - decay**count) + return map(lambda t: t / bias_correction.astype(t.dtype), moment) + + +def scale( + scalar: t.Union[float, numpy.floating, NDArray[numpy.floating]], + tree: Tree +) -> Tree: + return map(lambda x: scalar * x, tree) + + +def squared_norm( + tree: Tree +) -> NDArray[numpy.floating]: + return sum(map(lambda x: x**2, tree)) + + +@t.overload +@dataclass_transform(kw_only_default=False, frozen_default=False) +def tree_dataclass(cls: t.Type[T], /, *, + init: bool = True, kw_only: bool = False, frozen: bool = False, + static_fields: t.Sequence[str] = (), drop_fields: t.Sequence[str] = (), +) -> t.Type[T]: + ... + +@t.overload +@dataclass_transform(kw_only_default=False, frozen_default=False) +def tree_dataclass(*, + init: bool = True, kw_only: bool = False, frozen: bool = False, + static_fields: t.Sequence[str] = (), drop_fields: t.Sequence[str] = (), +) -> t.Callable[[t.Type[T]], t.Type[T]]: + ... + +def tree_dataclass(cls: t.Optional[t.Type[T]] = None, /, *, + init: bool = True, kw_only: bool = False, frozen: bool = False, + static_fields: t.Sequence[str] = (), drop_fields: t.Sequence[str] = (), +) -> t.Union[t.Type[T], t.Callable[[t.Type[T]], t.Type[T]]]: + if cls is None: + return lambda cls: tree_dataclass(cls, init=init, kw_only=kw_only, frozen=frozen, + static_fields=static_fields, drop_fields=drop_fields) + + cls = dataclasses.dataclass(init=init, kw_only=kw_only, frozen=frozen)(cls) + _register_dataclass(cls, static_fields=static_fields, drop_fields=drop_fields) + return cls + + +def _register_dataclass(cls: type, static_fields: t.Sequence[str], drop_fields: t.Sequence[str]): + fields = dataclasses.fields(cls) + field_names = {field.name for field in fields} + + if (extra := set(static_fields).difference(field_names)): + raise ValueError(f"Unknown field(s) passed to 'static_fields': {', '.join(map(repr, extra))}") + if (extra := set(drop_fields).difference(field_names)): + raise ValueError(f"Unknown field(s) passed to 'drop_fields': {', '.join(map(repr, extra))}") + + data_fields = tuple(field_names.difference(static_fields).difference(drop_fields)) + + def make_flatten_with_keys( + key_type: t.Callable[[str], Key] + ) -> t.Callable[[t.Any], t.Tuple[t.List[t.Tuple[Key, t.Any]], t.Hashable]]: + def flatten_with_keys(x: t.Any, /) -> tuple[list[tuple[Key, t.Any]], t.Hashable]: + meta = tuple(getattr(x, name) for name in static_fields) + trees = list((key_type(name), getattr(x, name)) for name in data_fields) + return trees, meta + + return flatten_with_keys + + def unflatten(meta: t.Hashable, trees: t.Iterable[t.Any], /) -> t.Any: + if not isinstance(meta, tuple): + raise TypeError + static_args = dict(zip(static_fields, meta, strict=True)) + data_args = dict(zip(data_fields, trees, strict=True)) + return cls(**static_args, **data_args) + + def flatten(x: t.Any, /) -> tuple[list[t.Any], t.Hashable]: + hashed = tuple(getattr(x, name) for name in static_fields) + trees = list(getattr(x, name) for name in data_fields) + return trees, hashed + + try: + from jax.tree_util import register_pytree_with_keys, GetAttrKey + except ImportError: + pass + else: + flatten_with_keys = make_flatten_with_keys(GetAttrKey) + register_pytree_with_keys(cls, flatten_with_keys, unflatten, flatten) + + try: + from torch.utils._pytree import register_pytree_node, GetAttrKey + except ImportError: + pass + else: + flatten_with_keys = make_flatten_with_keys(GetAttrKey) + register_pytree_node( + cls, flatten, lambda trees, meta: unflatten(meta, trees), + flatten_with_keys_fn=flatten_with_keys, # type: ignore + ) + + +__all__ = [ + 'flatten', 'flatten_with_path', 'unflatten', 'map', 'reduce', 'sum', + 'map_with_path', 'grad', 'value_and_grad', 'leaves', 'structure', 'leaves_with_path', + 'zeros_like', 'ones_like', 'full_like', 'cast', 'clip', 'conj', 'update_moment', + 'update_moment_per_elem_norm', 'bias_correction', 'scale', 'tree_dataclass', 'field', +] \ No newline at end of file diff --git a/phaser/web/dist/6dbbfc1fd1a453b71bec.module.wasm b/phaser/web/dist/6dbbfc1fd1a453b71bec.module.wasm new file mode 100644 index 0000000..62c4c8c Binary files /dev/null and b/phaser/web/dist/6dbbfc1fd1a453b71bec.module.wasm differ diff --git a/phaser/web/dist/bundle-dashboard.js b/phaser/web/dist/bundle-dashboard.js index b378dd3..72952fe 100644 --- a/phaser/web/dist/bundle-dashboard.js +++ b/phaser/web/dist/bundle-dashboard.js @@ -16,7 +16,7 @@ \******************************************************************/ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { -eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ arrow: () => (/* binding */ arrow),\n/* harmony export */ autoPlacement: () => (/* binding */ autoPlacement),\n/* harmony export */ computePosition: () => (/* binding */ computePosition),\n/* harmony export */ detectOverflow: () => (/* binding */ detectOverflow),\n/* harmony export */ flip: () => (/* binding */ flip),\n/* harmony export */ hide: () => (/* binding */ hide),\n/* harmony export */ inline: () => (/* binding */ inline),\n/* harmony export */ limitShift: () => (/* binding */ limitShift),\n/* harmony export */ offset: () => (/* binding */ offset),\n/* harmony export */ rectToClientRect: () => (/* reexport safe */ _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect),\n/* harmony export */ shift: () => (/* binding */ shift),\n/* harmony export */ size: () => (/* binding */ size)\n/* harmony export */ });\n/* harmony import */ var _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @floating-ui/utils */ \"./node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs\");\n\n\n\nfunction computeCoordsFromPlacement(_ref, placement, rtl) {\n let {\n reference,\n floating\n } = _ref;\n const sideAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement);\n const alignmentAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentAxis)(placement);\n const alignLength = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAxisLength)(alignmentAxis);\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const isVertical = sideAxis === 'y';\n const commonX = reference.x + reference.width / 2 - floating.width / 2;\n const commonY = reference.y + reference.height / 2 - floating.height / 2;\n const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;\n let coords;\n switch (side) {\n case 'top':\n coords = {\n x: commonX,\n y: reference.y - floating.height\n };\n break;\n case 'bottom':\n coords = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n case 'right':\n coords = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n case 'left':\n coords = {\n x: reference.x - floating.width,\n y: commonY\n };\n break;\n default:\n coords = {\n x: reference.x,\n y: reference.y\n };\n }\n switch ((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement)) {\n case 'start':\n coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);\n break;\n case 'end':\n coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1);\n break;\n }\n return coords;\n}\n\n/**\n * Computes the `x` and `y` coordinates that will place the floating element\n * next to a given reference element.\n *\n * This export does not have any `platform` interface logic. You will need to\n * write one for the platform you are using Floating UI with.\n */\nconst computePosition = async (reference, floating, config) => {\n const {\n placement = 'bottom',\n strategy = 'absolute',\n middleware = [],\n platform\n } = config;\n const validMiddleware = middleware.filter(Boolean);\n const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating));\n let rects = await platform.getElementRects({\n reference,\n floating,\n strategy\n });\n let {\n x,\n y\n } = computeCoordsFromPlacement(rects, placement, rtl);\n let statefulPlacement = placement;\n let middlewareData = {};\n let resetCount = 0;\n for (let i = 0; i < validMiddleware.length; i++) {\n const {\n name,\n fn\n } = validMiddleware[i];\n const {\n x: nextX,\n y: nextY,\n data,\n reset\n } = await fn({\n x,\n y,\n initialPlacement: placement,\n placement: statefulPlacement,\n strategy,\n middlewareData,\n rects,\n platform,\n elements: {\n reference,\n floating\n }\n });\n x = nextX != null ? nextX : x;\n y = nextY != null ? nextY : y;\n middlewareData = {\n ...middlewareData,\n [name]: {\n ...middlewareData[name],\n ...data\n }\n };\n if (reset && resetCount <= 50) {\n resetCount++;\n if (typeof reset === 'object') {\n if (reset.placement) {\n statefulPlacement = reset.placement;\n }\n if (reset.rects) {\n rects = reset.rects === true ? await platform.getElementRects({\n reference,\n floating,\n strategy\n }) : reset.rects;\n }\n ({\n x,\n y\n } = computeCoordsFromPlacement(rects, statefulPlacement, rtl));\n }\n i = -1;\n }\n }\n return {\n x,\n y,\n placement: statefulPlacement,\n strategy,\n middlewareData\n };\n};\n\n/**\n * Resolves with an object of overflow side offsets that determine how much the\n * element is overflowing a given clipping boundary on each side.\n * - positive = overflowing the boundary by that number of pixels\n * - negative = how many pixels left before it will overflow\n * - 0 = lies flush with the boundary\n * @see https://floating-ui.com/docs/detectOverflow\n */\nasync function detectOverflow(state, options) {\n var _await$platform$isEle;\n if (options === void 0) {\n options = {};\n }\n const {\n x,\n y,\n platform,\n rects,\n elements,\n strategy\n } = state;\n const {\n boundary = 'clippingAncestors',\n rootBoundary = 'viewport',\n elementContext = 'floating',\n altBoundary = false,\n padding = 0\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const paddingObject = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getPaddingObject)(padding);\n const altContext = elementContext === 'floating' ? 'reference' : 'floating';\n const element = elements[altBoundary ? altContext : elementContext];\n const clippingClientRect = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(await platform.getClippingRect({\n element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))),\n boundary,\n rootBoundary,\n strategy\n }));\n const rect = elementContext === 'floating' ? {\n x,\n y,\n width: rects.floating.width,\n height: rects.floating.height\n } : rects.reference;\n const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating));\n const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || {\n x: 1,\n y: 1\n } : {\n x: 1,\n y: 1\n };\n const elementClientRect = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({\n elements,\n rect,\n offsetParent,\n strategy\n }) : rect);\n return {\n top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,\n bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,\n left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,\n right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x\n };\n}\n\n/**\n * Provides data to position an inner element of the floating element so that it\n * appears centered to the reference element.\n * @see https://floating-ui.com/docs/arrow\n */\nconst arrow = options => ({\n name: 'arrow',\n options,\n async fn(state) {\n const {\n x,\n y,\n placement,\n rects,\n platform,\n elements,\n middlewareData\n } = state;\n // Since `element` is required, we don't Partial<> the type.\n const {\n element,\n padding = 0\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state) || {};\n if (element == null) {\n return {};\n }\n const paddingObject = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getPaddingObject)(padding);\n const coords = {\n x,\n y\n };\n const axis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentAxis)(placement);\n const length = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAxisLength)(axis);\n const arrowDimensions = await platform.getDimensions(element);\n const isYAxis = axis === 'y';\n const minProp = isYAxis ? 'top' : 'left';\n const maxProp = isYAxis ? 'bottom' : 'right';\n const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';\n const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];\n const startDiff = coords[axis] - rects.reference[axis];\n const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element));\n let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0;\n\n // DOM platform can return `window` as the `offsetParent`.\n if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) {\n clientSize = elements.floating[clientProp] || rects.floating[length];\n }\n const centerToReference = endDiff / 2 - startDiff / 2;\n\n // If the padding is large enough that it causes the arrow to no longer be\n // centered, modify the padding so that it is centered.\n const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;\n const minPadding = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(paddingObject[minProp], largestPossiblePadding);\n const maxPadding = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(paddingObject[maxProp], largestPossiblePadding);\n\n // Make sure the arrow doesn't overflow the floating element if the center\n // point is outside the floating element's bounds.\n const min$1 = minPadding;\n const max = clientSize - arrowDimensions[length] - maxPadding;\n const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;\n const offset = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.clamp)(min$1, center, max);\n\n // If the reference is small enough that the arrow's padding causes it to\n // to point to nothing for an aligned placement, adjust the offset of the\n // floating element itself. To ensure `shift()` continues to take action,\n // a single reset is performed when this is true.\n const shouldAddOffset = !middlewareData.arrow && (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;\n const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0;\n return {\n [axis]: coords[axis] + alignmentOffset,\n data: {\n [axis]: offset,\n centerOffset: center - offset - alignmentOffset,\n ...(shouldAddOffset && {\n alignmentOffset\n })\n },\n reset: shouldAddOffset\n };\n }\n});\n\nfunction getPlacementList(alignment, autoAlignment, allowedPlacements) {\n const allowedPlacementsSortedByAlignment = alignment ? [...allowedPlacements.filter(placement => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) === alignment), ...allowedPlacements.filter(placement => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) !== alignment)] : allowedPlacements.filter(placement => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement) === placement);\n return allowedPlacementsSortedByAlignment.filter(placement => {\n if (alignment) {\n return (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) === alignment || (autoAlignment ? (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAlignmentPlacement)(placement) !== placement : false);\n }\n return true;\n });\n}\n/**\n * Optimizes the visibility of the floating element by choosing the placement\n * that has the most space available automatically, without needing to specify a\n * preferred placement. Alternative to `flip`.\n * @see https://floating-ui.com/docs/autoPlacement\n */\nconst autoPlacement = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'autoPlacement',\n options,\n async fn(state) {\n var _middlewareData$autoP, _middlewareData$autoP2, _placementsThatFitOnE;\n const {\n rects,\n middlewareData,\n placement,\n platform,\n elements\n } = state;\n const {\n crossAxis = false,\n alignment,\n allowedPlacements = _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.placements,\n autoAlignment = true,\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const placements$1 = alignment !== undefined || allowedPlacements === _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.placements ? getPlacementList(alignment || null, autoAlignment, allowedPlacements) : allowedPlacements;\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const currentIndex = ((_middlewareData$autoP = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP.index) || 0;\n const currentPlacement = placements$1[currentIndex];\n if (currentPlacement == null) {\n return {};\n }\n const alignmentSides = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentSides)(currentPlacement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)));\n\n // Make `computeCoords` start from the right place.\n if (placement !== currentPlacement) {\n return {\n reset: {\n placement: placements$1[0]\n }\n };\n }\n const currentOverflows = [overflow[(0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(currentPlacement)], overflow[alignmentSides[0]], overflow[alignmentSides[1]]];\n const allOverflows = [...(((_middlewareData$autoP2 = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP2.overflows) || []), {\n placement: currentPlacement,\n overflows: currentOverflows\n }];\n const nextPlacement = placements$1[currentIndex + 1];\n\n // There are more placements to check.\n if (nextPlacement) {\n return {\n data: {\n index: currentIndex + 1,\n overflows: allOverflows\n },\n reset: {\n placement: nextPlacement\n }\n };\n }\n const placementsSortedByMostSpace = allOverflows.map(d => {\n const alignment = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(d.placement);\n return [d.placement, alignment && crossAxis ?\n // Check along the mainAxis and main crossAxis side.\n d.overflows.slice(0, 2).reduce((acc, v) => acc + v, 0) :\n // Check only the mainAxis.\n d.overflows[0], d.overflows];\n }).sort((a, b) => a[1] - b[1]);\n const placementsThatFitOnEachSide = placementsSortedByMostSpace.filter(d => d[2].slice(0,\n // Aligned placements should not check their opposite crossAxis\n // side.\n (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(d[0]) ? 2 : 3).every(v => v <= 0));\n const resetPlacement = ((_placementsThatFitOnE = placementsThatFitOnEachSide[0]) == null ? void 0 : _placementsThatFitOnE[0]) || placementsSortedByMostSpace[0][0];\n if (resetPlacement !== placement) {\n return {\n data: {\n index: currentIndex + 1,\n overflows: allOverflows\n },\n reset: {\n placement: resetPlacement\n }\n };\n }\n return {};\n }\n };\n};\n\n/**\n * Optimizes the visibility of the floating element by flipping the `placement`\n * in order to keep it in view when the preferred placement(s) will overflow the\n * clipping boundary. Alternative to `autoPlacement`.\n * @see https://floating-ui.com/docs/flip\n */\nconst flip = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'flip',\n options,\n async fn(state) {\n var _middlewareData$arrow, _middlewareData$flip;\n const {\n placement,\n middlewareData,\n rects,\n initialPlacement,\n platform,\n elements\n } = state;\n const {\n mainAxis: checkMainAxis = true,\n crossAxis: checkCrossAxis = true,\n fallbackPlacements: specifiedFallbackPlacements,\n fallbackStrategy = 'bestFit',\n fallbackAxisSideDirection = 'none',\n flipAlignment = true,\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n\n // If a reset by the arrow was caused due to an alignment offset being\n // added, we should skip any logic now since `flip()` has already done its\n // work.\n // https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643\n if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {\n return {};\n }\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const initialSideAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(initialPlacement);\n const isBasePlacement = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(initialPlacement) === initialPlacement;\n const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));\n const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [(0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositePlacement)(initialPlacement)] : (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getExpandedPlacements)(initialPlacement));\n const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== 'none';\n if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) {\n fallbackPlacements.push(...(0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAxisPlacements)(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));\n }\n const placements = [initialPlacement, ...fallbackPlacements];\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const overflows = [];\n let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];\n if (checkMainAxis) {\n overflows.push(overflow[side]);\n }\n if (checkCrossAxis) {\n const sides = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentSides)(placement, rects, rtl);\n overflows.push(overflow[sides[0]], overflow[sides[1]]);\n }\n overflowsData = [...overflowsData, {\n placement,\n overflows\n }];\n\n // One or more sides is overflowing.\n if (!overflows.every(side => side <= 0)) {\n var _middlewareData$flip2, _overflowsData$filter;\n const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;\n const nextPlacement = placements[nextIndex];\n if (nextPlacement) {\n const ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(nextPlacement) : false;\n if (!ignoreCrossAxisOverflow ||\n // We leave the current main axis only if every placement on that axis\n // overflows the main axis.\n overflowsData.every(d => d.overflows[0] > 0 && (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(d.placement) === initialSideAxis)) {\n // Try next placement and re-run the lifecycle.\n return {\n data: {\n index: nextIndex,\n overflows: overflowsData\n },\n reset: {\n placement: nextPlacement\n }\n };\n }\n }\n\n // First, find the candidates that fit on the mainAxis side of overflow,\n // then find the placement that fits the best on the main crossAxis side.\n let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement;\n\n // Otherwise fallback.\n if (!resetPlacement) {\n switch (fallbackStrategy) {\n case 'bestFit':\n {\n var _overflowsData$filter2;\n const placement = (_overflowsData$filter2 = overflowsData.filter(d => {\n if (hasFallbackAxisSideDirection) {\n const currentSideAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(d.placement);\n return currentSideAxis === initialSideAxis ||\n // Create a bias to the `y` side axis due to horizontal\n // reading directions favoring greater width.\n currentSideAxis === 'y';\n }\n return true;\n }).map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0];\n if (placement) {\n resetPlacement = placement;\n }\n break;\n }\n case 'initialPlacement':\n resetPlacement = initialPlacement;\n break;\n }\n }\n if (placement !== resetPlacement) {\n return {\n reset: {\n placement: resetPlacement\n }\n };\n }\n }\n return {};\n }\n };\n};\n\nfunction getSideOffsets(overflow, rect) {\n return {\n top: overflow.top - rect.height,\n right: overflow.right - rect.width,\n bottom: overflow.bottom - rect.height,\n left: overflow.left - rect.width\n };\n}\nfunction isAnySideFullyClipped(overflow) {\n return _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.sides.some(side => overflow[side] >= 0);\n}\n/**\n * Provides data to hide the floating element in applicable situations, such as\n * when it is not in the same clipping context as the reference element.\n * @see https://floating-ui.com/docs/hide\n */\nconst hide = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'hide',\n options,\n async fn(state) {\n const {\n rects\n } = state;\n const {\n strategy = 'referenceHidden',\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n switch (strategy) {\n case 'referenceHidden':\n {\n const overflow = await detectOverflow(state, {\n ...detectOverflowOptions,\n elementContext: 'reference'\n });\n const offsets = getSideOffsets(overflow, rects.reference);\n return {\n data: {\n referenceHiddenOffsets: offsets,\n referenceHidden: isAnySideFullyClipped(offsets)\n }\n };\n }\n case 'escaped':\n {\n const overflow = await detectOverflow(state, {\n ...detectOverflowOptions,\n altBoundary: true\n });\n const offsets = getSideOffsets(overflow, rects.floating);\n return {\n data: {\n escapedOffsets: offsets,\n escaped: isAnySideFullyClipped(offsets)\n }\n };\n }\n default:\n {\n return {};\n }\n }\n }\n };\n};\n\nfunction getBoundingRect(rects) {\n const minX = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(...rects.map(rect => rect.left));\n const minY = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(...rects.map(rect => rect.top));\n const maxX = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(...rects.map(rect => rect.right));\n const maxY = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(...rects.map(rect => rect.bottom));\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY\n };\n}\nfunction getRectsByLine(rects) {\n const sortedRects = rects.slice().sort((a, b) => a.y - b.y);\n const groups = [];\n let prevRect = null;\n for (let i = 0; i < sortedRects.length; i++) {\n const rect = sortedRects[i];\n if (!prevRect || rect.y - prevRect.y > prevRect.height / 2) {\n groups.push([rect]);\n } else {\n groups[groups.length - 1].push(rect);\n }\n prevRect = rect;\n }\n return groups.map(rect => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(getBoundingRect(rect)));\n}\n/**\n * Provides improved positioning for inline reference elements that can span\n * over multiple lines, such as hyperlinks or range selections.\n * @see https://floating-ui.com/docs/inline\n */\nconst inline = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'inline',\n options,\n async fn(state) {\n const {\n placement,\n elements,\n rects,\n platform,\n strategy\n } = state;\n // A MouseEvent's client{X,Y} coords can be up to 2 pixels off a\n // ClientRect's bounds, despite the event listener being triggered. A\n // padding of 2 seems to handle this issue.\n const {\n padding = 2,\n x,\n y\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const nativeClientRects = Array.from((await (platform.getClientRects == null ? void 0 : platform.getClientRects(elements.reference))) || []);\n const clientRects = getRectsByLine(nativeClientRects);\n const fallback = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(getBoundingRect(nativeClientRects));\n const paddingObject = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getPaddingObject)(padding);\n function getBoundingClientRect() {\n // There are two rects and they are disjoined.\n if (clientRects.length === 2 && clientRects[0].left > clientRects[1].right && x != null && y != null) {\n // Find the first rect in which the point is fully inside.\n return clientRects.find(rect => x > rect.left - paddingObject.left && x < rect.right + paddingObject.right && y > rect.top - paddingObject.top && y < rect.bottom + paddingObject.bottom) || fallback;\n }\n\n // There are 2 or more connected rects.\n if (clientRects.length >= 2) {\n if ((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement) === 'y') {\n const firstRect = clientRects[0];\n const lastRect = clientRects[clientRects.length - 1];\n const isTop = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement) === 'top';\n const top = firstRect.top;\n const bottom = lastRect.bottom;\n const left = isTop ? firstRect.left : lastRect.left;\n const right = isTop ? firstRect.right : lastRect.right;\n const width = right - left;\n const height = bottom - top;\n return {\n top,\n bottom,\n left,\n right,\n width,\n height,\n x: left,\n y: top\n };\n }\n const isLeftSide = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement) === 'left';\n const maxRight = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(...clientRects.map(rect => rect.right));\n const minLeft = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(...clientRects.map(rect => rect.left));\n const measureRects = clientRects.filter(rect => isLeftSide ? rect.left === minLeft : rect.right === maxRight);\n const top = measureRects[0].top;\n const bottom = measureRects[measureRects.length - 1].bottom;\n const left = minLeft;\n const right = maxRight;\n const width = right - left;\n const height = bottom - top;\n return {\n top,\n bottom,\n left,\n right,\n width,\n height,\n x: left,\n y: top\n };\n }\n return fallback;\n }\n const resetRects = await platform.getElementRects({\n reference: {\n getBoundingClientRect\n },\n floating: elements.floating,\n strategy\n });\n if (rects.reference.x !== resetRects.reference.x || rects.reference.y !== resetRects.reference.y || rects.reference.width !== resetRects.reference.width || rects.reference.height !== resetRects.reference.height) {\n return {\n reset: {\n rects: resetRects\n }\n };\n }\n return {};\n }\n };\n};\n\nconst originSides = /*#__PURE__*/new Set(['left', 'top']);\n\n// For type backwards-compatibility, the `OffsetOptions` type was also\n// Derivable.\n\nasync function convertValueToCoords(state, options) {\n const {\n placement,\n platform,\n elements\n } = state;\n const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const alignment = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement);\n const isVertical = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement) === 'y';\n const mainAxisMulti = originSides.has(side) ? -1 : 1;\n const crossAxisMulti = rtl && isVertical ? -1 : 1;\n const rawValue = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n\n // eslint-disable-next-line prefer-const\n let {\n mainAxis,\n crossAxis,\n alignmentAxis\n } = typeof rawValue === 'number' ? {\n mainAxis: rawValue,\n crossAxis: 0,\n alignmentAxis: null\n } : {\n mainAxis: rawValue.mainAxis || 0,\n crossAxis: rawValue.crossAxis || 0,\n alignmentAxis: rawValue.alignmentAxis\n };\n if (alignment && typeof alignmentAxis === 'number') {\n crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis;\n }\n return isVertical ? {\n x: crossAxis * crossAxisMulti,\n y: mainAxis * mainAxisMulti\n } : {\n x: mainAxis * mainAxisMulti,\n y: crossAxis * crossAxisMulti\n };\n}\n\n/**\n * Modifies the placement by translating the floating element along the\n * specified axes.\n * A number (shorthand for `mainAxis` or distance), or an axes configuration\n * object may be passed.\n * @see https://floating-ui.com/docs/offset\n */\nconst offset = function (options) {\n if (options === void 0) {\n options = 0;\n }\n return {\n name: 'offset',\n options,\n async fn(state) {\n var _middlewareData$offse, _middlewareData$arrow;\n const {\n x,\n y,\n placement,\n middlewareData\n } = state;\n const diffCoords = await convertValueToCoords(state, options);\n\n // If the placement is the same and the arrow caused an alignment offset\n // then we don't need to change the positioning coordinates.\n if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {\n return {};\n }\n return {\n x: x + diffCoords.x,\n y: y + diffCoords.y,\n data: {\n ...diffCoords,\n placement\n }\n };\n }\n };\n};\n\n/**\n * Optimizes the visibility of the floating element by shifting it in order to\n * keep it in view when it will overflow the clipping boundary.\n * @see https://floating-ui.com/docs/shift\n */\nconst shift = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'shift',\n options,\n async fn(state) {\n const {\n x,\n y,\n placement\n } = state;\n const {\n mainAxis: checkMainAxis = true,\n crossAxis: checkCrossAxis = false,\n limiter = {\n fn: _ref => {\n let {\n x,\n y\n } = _ref;\n return {\n x,\n y\n };\n }\n },\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const coords = {\n x,\n y\n };\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const crossAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement));\n const mainAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAxis)(crossAxis);\n let mainAxisCoord = coords[mainAxis];\n let crossAxisCoord = coords[crossAxis];\n if (checkMainAxis) {\n const minSide = mainAxis === 'y' ? 'top' : 'left';\n const maxSide = mainAxis === 'y' ? 'bottom' : 'right';\n const min = mainAxisCoord + overflow[minSide];\n const max = mainAxisCoord - overflow[maxSide];\n mainAxisCoord = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.clamp)(min, mainAxisCoord, max);\n }\n if (checkCrossAxis) {\n const minSide = crossAxis === 'y' ? 'top' : 'left';\n const maxSide = crossAxis === 'y' ? 'bottom' : 'right';\n const min = crossAxisCoord + overflow[minSide];\n const max = crossAxisCoord - overflow[maxSide];\n crossAxisCoord = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.clamp)(min, crossAxisCoord, max);\n }\n const limitedCoords = limiter.fn({\n ...state,\n [mainAxis]: mainAxisCoord,\n [crossAxis]: crossAxisCoord\n });\n return {\n ...limitedCoords,\n data: {\n x: limitedCoords.x - x,\n y: limitedCoords.y - y,\n enabled: {\n [mainAxis]: checkMainAxis,\n [crossAxis]: checkCrossAxis\n }\n }\n };\n }\n };\n};\n/**\n * Built-in `limiter` that will stop `shift()` at a certain point.\n */\nconst limitShift = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n options,\n fn(state) {\n const {\n x,\n y,\n placement,\n rects,\n middlewareData\n } = state;\n const {\n offset = 0,\n mainAxis: checkMainAxis = true,\n crossAxis: checkCrossAxis = true\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const coords = {\n x,\n y\n };\n const crossAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement);\n const mainAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAxis)(crossAxis);\n let mainAxisCoord = coords[mainAxis];\n let crossAxisCoord = coords[crossAxis];\n const rawOffset = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(offset, state);\n const computedOffset = typeof rawOffset === 'number' ? {\n mainAxis: rawOffset,\n crossAxis: 0\n } : {\n mainAxis: 0,\n crossAxis: 0,\n ...rawOffset\n };\n if (checkMainAxis) {\n const len = mainAxis === 'y' ? 'height' : 'width';\n const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis;\n const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis;\n if (mainAxisCoord < limitMin) {\n mainAxisCoord = limitMin;\n } else if (mainAxisCoord > limitMax) {\n mainAxisCoord = limitMax;\n }\n }\n if (checkCrossAxis) {\n var _middlewareData$offse, _middlewareData$offse2;\n const len = mainAxis === 'y' ? 'width' : 'height';\n const isOriginSide = originSides.has((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement));\n const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis);\n const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0);\n if (crossAxisCoord < limitMin) {\n crossAxisCoord = limitMin;\n } else if (crossAxisCoord > limitMax) {\n crossAxisCoord = limitMax;\n }\n }\n return {\n [mainAxis]: mainAxisCoord,\n [crossAxis]: crossAxisCoord\n };\n }\n };\n};\n\n/**\n * Provides data that allows you to change the size of the floating element —\n * for instance, prevent it from overflowing the clipping boundary or match the\n * width of the reference element.\n * @see https://floating-ui.com/docs/size\n */\nconst size = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'size',\n options,\n async fn(state) {\n var _state$middlewareData, _state$middlewareData2;\n const {\n placement,\n rects,\n platform,\n elements\n } = state;\n const {\n apply = () => {},\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const alignment = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement);\n const isYAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement) === 'y';\n const {\n width,\n height\n } = rects.floating;\n let heightSide;\n let widthSide;\n if (side === 'top' || side === 'bottom') {\n heightSide = side;\n widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right';\n } else {\n widthSide = side;\n heightSide = alignment === 'end' ? 'top' : 'bottom';\n }\n const maximumClippingHeight = height - overflow.top - overflow.bottom;\n const maximumClippingWidth = width - overflow.left - overflow.right;\n const overflowAvailableHeight = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(height - overflow[heightSide], maximumClippingHeight);\n const overflowAvailableWidth = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(width - overflow[widthSide], maximumClippingWidth);\n const noShift = !state.middlewareData.shift;\n let availableHeight = overflowAvailableHeight;\n let availableWidth = overflowAvailableWidth;\n if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) {\n availableWidth = maximumClippingWidth;\n }\n if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) {\n availableHeight = maximumClippingHeight;\n }\n if (noShift && !alignment) {\n const xMin = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.left, 0);\n const xMax = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.right, 0);\n const yMin = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.top, 0);\n const yMax = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.bottom, 0);\n if (isYAxis) {\n availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.left, overflow.right));\n } else {\n availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.top, overflow.bottom));\n }\n }\n await apply({\n ...state,\n availableWidth,\n availableHeight\n });\n const nextDimensions = await platform.getDimensions(elements.floating);\n if (width !== nextDimensions.width || height !== nextDimensions.height) {\n return {\n reset: {\n rects: true\n }\n };\n }\n return {};\n }\n };\n};\n\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGZsb2F0aW5nLXVpL2NvcmUvZGlzdC9mbG9hdGluZy11aS5jb3JlLm1qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQXNVO0FBQ2hSOztBQUV0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSixtQkFBbUIsK0RBQVc7QUFDOUIsd0JBQXdCLG9FQUFnQjtBQUN4QyxzQkFBc0IsaUVBQWE7QUFDbkMsZUFBZSwyREFBTztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVLGdFQUFZO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQiw0QkFBNEI7QUFDOUM7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSSxFQUFFLDREQUFRO0FBQ2Qsd0JBQXdCLG9FQUFnQjtBQUN4QztBQUNBO0FBQ0EsNkJBQTZCLG9FQUFnQjtBQUM3QztBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixvRUFBZ0I7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU0sRUFBRSw0REFBUTtBQUNoQjtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIsb0VBQWdCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsaUJBQWlCLG9FQUFnQjtBQUNqQyxtQkFBbUIsaUVBQWE7QUFDaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsdUJBQXVCLHVEQUFHO0FBQzFCLHVCQUF1Qix1REFBRzs7QUFFMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFtQix5REFBSzs7QUFFeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxREFBcUQsZ0VBQVk7QUFDakU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0EsQ0FBQzs7QUFFRDtBQUNBLG1HQUFtRyxnRUFBWSxxRUFBcUUsZ0VBQVksb0VBQW9FLDJEQUFPO0FBQzNRO0FBQ0E7QUFDQSxhQUFhLGdFQUFZLDhDQUE4QyxpRkFBNkI7QUFDcEc7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsMERBQVU7QUFDdEM7QUFDQTtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQiw0RUFBNEUsMERBQVU7QUFDdEY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkJBQTZCLHFFQUFpQjs7QUFFOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF5QywyREFBTztBQUNoRDtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUEwQixnRUFBWTtBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE1BQU0sZ0VBQVk7QUFDbEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUSxFQUFFLDREQUFROztBQUVsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFtQiwyREFBTztBQUMxQiw4QkFBOEIsK0RBQVc7QUFDekMsOEJBQThCLDJEQUFPO0FBQ3JDO0FBQ0Esc0dBQXNHLHdFQUFvQixzQkFBc0IseUVBQXFCO0FBQ3JLO0FBQ0E7QUFDQSxtQ0FBbUMsNkVBQXlCO0FBQzVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixxRUFBaUI7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87O0FBRVA7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0ZBQStGLCtEQUFXO0FBQzFHO0FBQ0E7QUFDQTtBQUNBLHlEQUF5RCwrREFBVztBQUNwRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZTtBQUNmO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRDQUE0QywrREFBVztBQUN2RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUyxxREFBSztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxlQUFlLHVEQUFHO0FBQ2xCLGVBQWUsdURBQUc7QUFDbEIsZUFBZSx1REFBRztBQUNsQixlQUFlLHVEQUFHO0FBQ2xCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0Isd0JBQXdCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixvRUFBZ0I7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUiwrQkFBK0IsS0FBSztBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRLEVBQUUsNERBQVE7QUFDbEI7QUFDQTtBQUNBLHVCQUF1QixvRUFBZ0I7QUFDdkMsNEJBQTRCLG9FQUFnQjtBQUM1QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGNBQWMsK0RBQVc7QUFDekI7QUFDQTtBQUNBLDBCQUEwQiwyREFBTztBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkJBQTZCLDJEQUFPO0FBQ3BDLDJCQUEyQix1REFBRztBQUM5QiwwQkFBMEIsdURBQUc7QUFDN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0EsZUFBZSwyREFBTztBQUN0QixvQkFBb0IsZ0VBQVk7QUFDaEMscUJBQXFCLCtEQUFXO0FBQ2hDO0FBQ0E7QUFDQSxtQkFBbUIsNERBQVE7O0FBRTNCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLCtEQUFXLENBQUMsMkRBQU87QUFDM0MsdUJBQXVCLG1FQUFlO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLHlEQUFLO0FBQzdCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlCQUF5Qix5REFBSztBQUM5QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QiwrREFBVztBQUNuQyx1QkFBdUIsbUVBQWU7QUFDdEM7QUFDQTtBQUNBLHdCQUF3Qiw0REFBUTtBQUNoQztBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLDJEQUFPO0FBQ3BEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSx3QkFBd0I7QUFDeEI7QUFDQSxRQUFRLEVBQUUsNERBQVE7QUFDbEI7QUFDQSxtQkFBbUIsMkRBQU87QUFDMUIsd0JBQXdCLGdFQUFZO0FBQ3BDLHNCQUFzQiwrREFBVztBQUNqQztBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLHVEQUFHO0FBQ3pDLHFDQUFxQyx1REFBRztBQUN4QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFCQUFxQix1REFBRztBQUN4QixxQkFBcUIsdURBQUc7QUFDeEIscUJBQXFCLHVEQUFHO0FBQ3hCLHFCQUFxQix1REFBRztBQUN4QjtBQUNBLGlGQUFpRix1REFBRztBQUNwRixVQUFVO0FBQ1YsbUZBQW1GLHVEQUFHO0FBQ3RGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRXNIIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vcGhhc2VyLWZyb250ZW5kLy4vbm9kZV9tb2R1bGVzL0BmbG9hdGluZy11aS9jb3JlL2Rpc3QvZmxvYXRpbmctdWkuY29yZS5tanM/ZTRjNyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBnZXRTaWRlQXhpcywgZ2V0QWxpZ25tZW50QXhpcywgZ2V0QXhpc0xlbmd0aCwgZ2V0U2lkZSwgZ2V0QWxpZ25tZW50LCBldmFsdWF0ZSwgZ2V0UGFkZGluZ09iamVjdCwgcmVjdFRvQ2xpZW50UmVjdCwgbWluLCBjbGFtcCwgcGxhY2VtZW50cywgZ2V0QWxpZ25tZW50U2lkZXMsIGdldE9wcG9zaXRlQWxpZ25tZW50UGxhY2VtZW50LCBnZXRPcHBvc2l0ZVBsYWNlbWVudCwgZ2V0RXhwYW5kZWRQbGFjZW1lbnRzLCBnZXRPcHBvc2l0ZUF4aXNQbGFjZW1lbnRzLCBzaWRlcywgbWF4LCBnZXRPcHBvc2l0ZUF4aXMgfSBmcm9tICdAZmxvYXRpbmctdWkvdXRpbHMnO1xuZXhwb3J0IHsgcmVjdFRvQ2xpZW50UmVjdCB9IGZyb20gJ0BmbG9hdGluZy11aS91dGlscyc7XG5cbmZ1bmN0aW9uIGNvbXB1dGVDb29yZHNGcm9tUGxhY2VtZW50KF9yZWYsIHBsYWNlbWVudCwgcnRsKSB7XG4gIGxldCB7XG4gICAgcmVmZXJlbmNlLFxuICAgIGZsb2F0aW5nXG4gIH0gPSBfcmVmO1xuICBjb25zdCBzaWRlQXhpcyA9IGdldFNpZGVBeGlzKHBsYWNlbWVudCk7XG4gIGNvbnN0IGFsaWdubWVudEF4aXMgPSBnZXRBbGlnbm1lbnRBeGlzKHBsYWNlbWVudCk7XG4gIGNvbnN0IGFsaWduTGVuZ3RoID0gZ2V0QXhpc0xlbmd0aChhbGlnbm1lbnRBeGlzKTtcbiAgY29uc3Qgc2lkZSA9IGdldFNpZGUocGxhY2VtZW50KTtcbiAgY29uc3QgaXNWZXJ0aWNhbCA9IHNpZGVBeGlzID09PSAneSc7XG4gIGNvbnN0IGNvbW1vblggPSByZWZlcmVuY2UueCArIHJlZmVyZW5jZS53aWR0aCAvIDIgLSBmbG9hdGluZy53aWR0aCAvIDI7XG4gIGNvbnN0IGNvbW1vblkgPSByZWZlcmVuY2UueSArIHJlZmVyZW5jZS5oZWlnaHQgLyAyIC0gZmxvYXRpbmcuaGVpZ2h0IC8gMjtcbiAgY29uc3QgY29tbW9uQWxpZ24gPSByZWZlcmVuY2VbYWxpZ25MZW5ndGhdIC8gMiAtIGZsb2F0aW5nW2FsaWduTGVuZ3RoXSAvIDI7XG4gIGxldCBjb29yZHM7XG4gIHN3aXRjaCAoc2lkZSkge1xuICAgIGNhc2UgJ3RvcCc6XG4gICAgICBjb29yZHMgPSB7XG4gICAgICAgIHg6IGNvbW1vblgsXG4gICAgICAgIHk6IHJlZmVyZW5jZS55IC0gZmxvYXRpbmcuaGVpZ2h0XG4gICAgICB9O1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnYm90dG9tJzpcbiAgICAgIGNvb3JkcyA9IHtcbiAgICAgICAgeDogY29tbW9uWCxcbiAgICAgICAgeTogcmVmZXJlbmNlLnkgKyByZWZlcmVuY2UuaGVpZ2h0XG4gICAgICB9O1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAncmlnaHQnOlxuICAgICAgY29vcmRzID0ge1xuICAgICAgICB4OiByZWZlcmVuY2UueCArIHJlZmVyZW5jZS53aWR0aCxcbiAgICAgICAgeTogY29tbW9uWVxuICAgICAgfTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ2xlZnQnOlxuICAgICAgY29vcmRzID0ge1xuICAgICAgICB4OiByZWZlcmVuY2UueCAtIGZsb2F0aW5nLndpZHRoLFxuICAgICAgICB5OiBjb21tb25ZXG4gICAgICB9O1xuICAgICAgYnJlYWs7XG4gICAgZGVmYXVsdDpcbiAgICAgIGNvb3JkcyA9IHtcbiAgICAgICAgeDogcmVmZXJlbmNlLngsXG4gICAgICAgIHk6IHJlZmVyZW5jZS55XG4gICAgICB9O1xuICB9XG4gIHN3aXRjaCAoZ2V0QWxpZ25tZW50KHBsYWNlbWVudCkpIHtcbiAgICBjYXNlICdzdGFydCc6XG4gICAgICBjb29yZHNbYWxpZ25tZW50QXhpc10gLT0gY29tbW9uQWxpZ24gKiAocnRsICYmIGlzVmVydGljYWwgPyAtMSA6IDEpO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnZW5kJzpcbiAgICAgIGNvb3Jkc1thbGlnbm1lbnRBeGlzXSArPSBjb21tb25BbGlnbiAqIChydGwgJiYgaXNWZXJ0aWNhbCA/IC0xIDogMSk7XG4gICAgICBicmVhaztcbiAgfVxuICByZXR1cm4gY29vcmRzO1xufVxuXG4vKipcbiAqIENvbXB1dGVzIHRoZSBgeGAgYW5kIGB5YCBjb29yZGluYXRlcyB0aGF0IHdpbGwgcGxhY2UgdGhlIGZsb2F0aW5nIGVsZW1lbnRcbiAqIG5leHQgdG8gYSBnaXZlbiByZWZlcmVuY2UgZWxlbWVudC5cbiAqXG4gKiBUaGlzIGV4cG9ydCBkb2VzIG5vdCBoYXZlIGFueSBgcGxhdGZvcm1gIGludGVyZmFjZSBsb2dpYy4gWW91IHdpbGwgbmVlZCB0b1xuICogd3JpdGUgb25lIGZvciB0aGUgcGxhdGZvcm0geW91IGFyZSB1c2luZyBGbG9hdGluZyBVSSB3aXRoLlxuICovXG5jb25zdCBjb21wdXRlUG9zaXRpb24gPSBhc3luYyAocmVmZXJlbmNlLCBmbG9hdGluZywgY29uZmlnKSA9PiB7XG4gIGNvbnN0IHtcbiAgICBwbGFjZW1lbnQgPSAnYm90dG9tJyxcbiAgICBzdHJhdGVneSA9ICdhYnNvbHV0ZScsXG4gICAgbWlkZGxld2FyZSA9IFtdLFxuICAgIHBsYXRmb3JtXG4gIH0gPSBjb25maWc7XG4gIGNvbnN0IHZhbGlkTWlkZGxld2FyZSA9IG1pZGRsZXdhcmUuZmlsdGVyKEJvb2xlYW4pO1xuICBjb25zdCBydGwgPSBhd2FpdCAocGxhdGZvcm0uaXNSVEwgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmlzUlRMKGZsb2F0aW5nKSk7XG4gIGxldCByZWN0cyA9IGF3YWl0IHBsYXRmb3JtLmdldEVsZW1lbnRSZWN0cyh7XG4gICAgcmVmZXJlbmNlLFxuICAgIGZsb2F0aW5nLFxuICAgIHN0cmF0ZWd5XG4gIH0pO1xuICBsZXQge1xuICAgIHgsXG4gICAgeVxuICB9ID0gY29tcHV0ZUNvb3Jkc0Zyb21QbGFjZW1lbnQocmVjdHMsIHBsYWNlbWVudCwgcnRsKTtcbiAgbGV0IHN0YXRlZnVsUGxhY2VtZW50ID0gcGxhY2VtZW50O1xuICBsZXQgbWlkZGxld2FyZURhdGEgPSB7fTtcbiAgbGV0IHJlc2V0Q291bnQgPSAwO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHZhbGlkTWlkZGxld2FyZS5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IHtcbiAgICAgIG5hbWUsXG4gICAgICBmblxuICAgIH0gPSB2YWxpZE1pZGRsZXdhcmVbaV07XG4gICAgY29uc3Qge1xuICAgICAgeDogbmV4dFgsXG4gICAgICB5OiBuZXh0WSxcbiAgICAgIGRhdGEsXG4gICAgICByZXNldFxuICAgIH0gPSBhd2FpdCBmbih7XG4gICAgICB4LFxuICAgICAgeSxcbiAgICAgIGluaXRpYWxQbGFjZW1lbnQ6IHBsYWNlbWVudCxcbiAgICAgIHBsYWNlbWVudDogc3RhdGVmdWxQbGFjZW1lbnQsXG4gICAgICBzdHJhdGVneSxcbiAgICAgIG1pZGRsZXdhcmVEYXRhLFxuICAgICAgcmVjdHMsXG4gICAgICBwbGF0Zm9ybSxcbiAgICAgIGVsZW1lbnRzOiB7XG4gICAgICAgIHJlZmVyZW5jZSxcbiAgICAgICAgZmxvYXRpbmdcbiAgICAgIH1cbiAgICB9KTtcbiAgICB4ID0gbmV4dFggIT0gbnVsbCA/IG5leHRYIDogeDtcbiAgICB5ID0gbmV4dFkgIT0gbnVsbCA/IG5leHRZIDogeTtcbiAgICBtaWRkbGV3YXJlRGF0YSA9IHtcbiAgICAgIC4uLm1pZGRsZXdhcmVEYXRhLFxuICAgICAgW25hbWVdOiB7XG4gICAgICAgIC4uLm1pZGRsZXdhcmVEYXRhW25hbWVdLFxuICAgICAgICAuLi5kYXRhXG4gICAgICB9XG4gICAgfTtcbiAgICBpZiAocmVzZXQgJiYgcmVzZXRDb3VudCA8PSA1MCkge1xuICAgICAgcmVzZXRDb3VudCsrO1xuICAgICAgaWYgKHR5cGVvZiByZXNldCA9PT0gJ29iamVjdCcpIHtcbiAgICAgICAgaWYgKHJlc2V0LnBsYWNlbWVudCkge1xuICAgICAgICAgIHN0YXRlZnVsUGxhY2VtZW50ID0gcmVzZXQucGxhY2VtZW50O1xuICAgICAgICB9XG4gICAgICAgIGlmIChyZXNldC5yZWN0cykge1xuICAgICAgICAgIHJlY3RzID0gcmVzZXQucmVjdHMgPT09IHRydWUgPyBhd2FpdCBwbGF0Zm9ybS5nZXRFbGVtZW50UmVjdHMoe1xuICAgICAgICAgICAgcmVmZXJlbmNlLFxuICAgICAgICAgICAgZmxvYXRpbmcsXG4gICAgICAgICAgICBzdHJhdGVneVxuICAgICAgICAgIH0pIDogcmVzZXQucmVjdHM7XG4gICAgICAgIH1cbiAgICAgICAgKHtcbiAgICAgICAgICB4LFxuICAgICAgICAgIHlcbiAgICAgICAgfSA9IGNvbXB1dGVDb29yZHNGcm9tUGxhY2VtZW50KHJlY3RzLCBzdGF0ZWZ1bFBsYWNlbWVudCwgcnRsKSk7XG4gICAgICB9XG4gICAgICBpID0gLTE7XG4gICAgfVxuICB9XG4gIHJldHVybiB7XG4gICAgeCxcbiAgICB5LFxuICAgIHBsYWNlbWVudDogc3RhdGVmdWxQbGFjZW1lbnQsXG4gICAgc3RyYXRlZ3ksXG4gICAgbWlkZGxld2FyZURhdGFcbiAgfTtcbn07XG5cbi8qKlxuICogUmVzb2x2ZXMgd2l0aCBhbiBvYmplY3Qgb2Ygb3ZlcmZsb3cgc2lkZSBvZmZzZXRzIHRoYXQgZGV0ZXJtaW5lIGhvdyBtdWNoIHRoZVxuICogZWxlbWVudCBpcyBvdmVyZmxvd2luZyBhIGdpdmVuIGNsaXBwaW5nIGJvdW5kYXJ5IG9uIGVhY2ggc2lkZS5cbiAqIC0gcG9zaXRpdmUgPSBvdmVyZmxvd2luZyB0aGUgYm91bmRhcnkgYnkgdGhhdCBudW1iZXIgb2YgcGl4ZWxzXG4gKiAtIG5lZ2F0aXZlID0gaG93IG1hbnkgcGl4ZWxzIGxlZnQgYmVmb3JlIGl0IHdpbGwgb3ZlcmZsb3dcbiAqIC0gMCA9IGxpZXMgZmx1c2ggd2l0aCB0aGUgYm91bmRhcnlcbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9kZXRlY3RPdmVyZmxvd1xuICovXG5hc3luYyBmdW5jdGlvbiBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwgb3B0aW9ucykge1xuICB2YXIgX2F3YWl0JHBsYXRmb3JtJGlzRWxlO1xuICBpZiAob3B0aW9ucyA9PT0gdm9pZCAwKSB7XG4gICAgb3B0aW9ucyA9IHt9O1xuICB9XG4gIGNvbnN0IHtcbiAgICB4LFxuICAgIHksXG4gICAgcGxhdGZvcm0sXG4gICAgcmVjdHMsXG4gICAgZWxlbWVudHMsXG4gICAgc3RyYXRlZ3lcbiAgfSA9IHN0YXRlO1xuICBjb25zdCB7XG4gICAgYm91bmRhcnkgPSAnY2xpcHBpbmdBbmNlc3RvcnMnLFxuICAgIHJvb3RCb3VuZGFyeSA9ICd2aWV3cG9ydCcsXG4gICAgZWxlbWVudENvbnRleHQgPSAnZmxvYXRpbmcnLFxuICAgIGFsdEJvdW5kYXJ5ID0gZmFsc2UsXG4gICAgcGFkZGluZyA9IDBcbiAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKTtcbiAgY29uc3QgcGFkZGluZ09iamVjdCA9IGdldFBhZGRpbmdPYmplY3QocGFkZGluZyk7XG4gIGNvbnN0IGFsdENvbnRleHQgPSBlbGVtZW50Q29udGV4dCA9PT0gJ2Zsb2F0aW5nJyA/ICdyZWZlcmVuY2UnIDogJ2Zsb2F0aW5nJztcbiAgY29uc3QgZWxlbWVudCA9IGVsZW1lbnRzW2FsdEJvdW5kYXJ5ID8gYWx0Q29udGV4dCA6IGVsZW1lbnRDb250ZXh0XTtcbiAgY29uc3QgY2xpcHBpbmdDbGllbnRSZWN0ID0gcmVjdFRvQ2xpZW50UmVjdChhd2FpdCBwbGF0Zm9ybS5nZXRDbGlwcGluZ1JlY3Qoe1xuICAgIGVsZW1lbnQ6ICgoX2F3YWl0JHBsYXRmb3JtJGlzRWxlID0gYXdhaXQgKHBsYXRmb3JtLmlzRWxlbWVudCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNFbGVtZW50KGVsZW1lbnQpKSkgIT0gbnVsbCA/IF9hd2FpdCRwbGF0Zm9ybSRpc0VsZSA6IHRydWUpID8gZWxlbWVudCA6IGVsZW1lbnQuY29udGV4dEVsZW1lbnQgfHwgKGF3YWl0IChwbGF0Zm9ybS5nZXREb2N1bWVudEVsZW1lbnQgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmdldERvY3VtZW50RWxlbWVudChlbGVtZW50cy5mbG9hdGluZykpKSxcbiAgICBib3VuZGFyeSxcbiAgICByb290Qm91bmRhcnksXG4gICAgc3RyYXRlZ3lcbiAgfSkpO1xuICBjb25zdCByZWN0ID0gZWxlbWVudENvbnRleHQgPT09ICdmbG9hdGluZycgPyB7XG4gICAgeCxcbiAgICB5LFxuICAgIHdpZHRoOiByZWN0cy5mbG9hdGluZy53aWR0aCxcbiAgICBoZWlnaHQ6IHJlY3RzLmZsb2F0aW5nLmhlaWdodFxuICB9IDogcmVjdHMucmVmZXJlbmNlO1xuICBjb25zdCBvZmZzZXRQYXJlbnQgPSBhd2FpdCAocGxhdGZvcm0uZ2V0T2Zmc2V0UGFyZW50ID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5nZXRPZmZzZXRQYXJlbnQoZWxlbWVudHMuZmxvYXRpbmcpKTtcbiAgY29uc3Qgb2Zmc2V0U2NhbGUgPSAoYXdhaXQgKHBsYXRmb3JtLmlzRWxlbWVudCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNFbGVtZW50KG9mZnNldFBhcmVudCkpKSA/IChhd2FpdCAocGxhdGZvcm0uZ2V0U2NhbGUgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmdldFNjYWxlKG9mZnNldFBhcmVudCkpKSB8fCB7XG4gICAgeDogMSxcbiAgICB5OiAxXG4gIH0gOiB7XG4gICAgeDogMSxcbiAgICB5OiAxXG4gIH07XG4gIGNvbnN0IGVsZW1lbnRDbGllbnRSZWN0ID0gcmVjdFRvQ2xpZW50UmVjdChwbGF0Zm9ybS5jb252ZXJ0T2Zmc2V0UGFyZW50UmVsYXRpdmVSZWN0VG9WaWV3cG9ydFJlbGF0aXZlUmVjdCA/IGF3YWl0IHBsYXRmb3JtLmNvbnZlcnRPZmZzZXRQYXJlbnRSZWxhdGl2ZVJlY3RUb1ZpZXdwb3J0UmVsYXRpdmVSZWN0KHtcbiAgICBlbGVtZW50cyxcbiAgICByZWN0LFxuICAgIG9mZnNldFBhcmVudCxcbiAgICBzdHJhdGVneVxuICB9KSA6IHJlY3QpO1xuICByZXR1cm4ge1xuICAgIHRvcDogKGNsaXBwaW5nQ2xpZW50UmVjdC50b3AgLSBlbGVtZW50Q2xpZW50UmVjdC50b3AgKyBwYWRkaW5nT2JqZWN0LnRvcCkgLyBvZmZzZXRTY2FsZS55LFxuICAgIGJvdHRvbTogKGVsZW1lbnRDbGllbnRSZWN0LmJvdHRvbSAtIGNsaXBwaW5nQ2xpZW50UmVjdC5ib3R0b20gKyBwYWRkaW5nT2JqZWN0LmJvdHRvbSkgLyBvZmZzZXRTY2FsZS55LFxuICAgIGxlZnQ6IChjbGlwcGluZ0NsaWVudFJlY3QubGVmdCAtIGVsZW1lbnRDbGllbnRSZWN0LmxlZnQgKyBwYWRkaW5nT2JqZWN0LmxlZnQpIC8gb2Zmc2V0U2NhbGUueCxcbiAgICByaWdodDogKGVsZW1lbnRDbGllbnRSZWN0LnJpZ2h0IC0gY2xpcHBpbmdDbGllbnRSZWN0LnJpZ2h0ICsgcGFkZGluZ09iamVjdC5yaWdodCkgLyBvZmZzZXRTY2FsZS54XG4gIH07XG59XG5cbi8qKlxuICogUHJvdmlkZXMgZGF0YSB0byBwb3NpdGlvbiBhbiBpbm5lciBlbGVtZW50IG9mIHRoZSBmbG9hdGluZyBlbGVtZW50IHNvIHRoYXQgaXRcbiAqIGFwcGVhcnMgY2VudGVyZWQgdG8gdGhlIHJlZmVyZW5jZSBlbGVtZW50LlxuICogQHNlZSBodHRwczovL2Zsb2F0aW5nLXVpLmNvbS9kb2NzL2Fycm93XG4gKi9cbmNvbnN0IGFycm93ID0gb3B0aW9ucyA9PiAoe1xuICBuYW1lOiAnYXJyb3cnLFxuICBvcHRpb25zLFxuICBhc3luYyBmbihzdGF0ZSkge1xuICAgIGNvbnN0IHtcbiAgICAgIHgsXG4gICAgICB5LFxuICAgICAgcGxhY2VtZW50LFxuICAgICAgcmVjdHMsXG4gICAgICBwbGF0Zm9ybSxcbiAgICAgIGVsZW1lbnRzLFxuICAgICAgbWlkZGxld2FyZURhdGFcbiAgICB9ID0gc3RhdGU7XG4gICAgLy8gU2luY2UgYGVsZW1lbnRgIGlzIHJlcXVpcmVkLCB3ZSBkb24ndCBQYXJ0aWFsPD4gdGhlIHR5cGUuXG4gICAgY29uc3Qge1xuICAgICAgZWxlbWVudCxcbiAgICAgIHBhZGRpbmcgPSAwXG4gICAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKSB8fCB7fTtcbiAgICBpZiAoZWxlbWVudCA9PSBudWxsKSB7XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuICAgIGNvbnN0IHBhZGRpbmdPYmplY3QgPSBnZXRQYWRkaW5nT2JqZWN0KHBhZGRpbmcpO1xuICAgIGNvbnN0IGNvb3JkcyA9IHtcbiAgICAgIHgsXG4gICAgICB5XG4gICAgfTtcbiAgICBjb25zdCBheGlzID0gZ2V0QWxpZ25tZW50QXhpcyhwbGFjZW1lbnQpO1xuICAgIGNvbnN0IGxlbmd0aCA9IGdldEF4aXNMZW5ndGgoYXhpcyk7XG4gICAgY29uc3QgYXJyb3dEaW1lbnNpb25zID0gYXdhaXQgcGxhdGZvcm0uZ2V0RGltZW5zaW9ucyhlbGVtZW50KTtcbiAgICBjb25zdCBpc1lBeGlzID0gYXhpcyA9PT0gJ3knO1xuICAgIGNvbnN0IG1pblByb3AgPSBpc1lBeGlzID8gJ3RvcCcgOiAnbGVmdCc7XG4gICAgY29uc3QgbWF4UHJvcCA9IGlzWUF4aXMgPyAnYm90dG9tJyA6ICdyaWdodCc7XG4gICAgY29uc3QgY2xpZW50UHJvcCA9IGlzWUF4aXMgPyAnY2xpZW50SGVpZ2h0JyA6ICdjbGllbnRXaWR0aCc7XG4gICAgY29uc3QgZW5kRGlmZiA9IHJlY3RzLnJlZmVyZW5jZVtsZW5ndGhdICsgcmVjdHMucmVmZXJlbmNlW2F4aXNdIC0gY29vcmRzW2F4aXNdIC0gcmVjdHMuZmxvYXRpbmdbbGVuZ3RoXTtcbiAgICBjb25zdCBzdGFydERpZmYgPSBjb29yZHNbYXhpc10gLSByZWN0cy5yZWZlcmVuY2VbYXhpc107XG4gICAgY29uc3QgYXJyb3dPZmZzZXRQYXJlbnQgPSBhd2FpdCAocGxhdGZvcm0uZ2V0T2Zmc2V0UGFyZW50ID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5nZXRPZmZzZXRQYXJlbnQoZWxlbWVudCkpO1xuICAgIGxldCBjbGllbnRTaXplID0gYXJyb3dPZmZzZXRQYXJlbnQgPyBhcnJvd09mZnNldFBhcmVudFtjbGllbnRQcm9wXSA6IDA7XG5cbiAgICAvLyBET00gcGxhdGZvcm0gY2FuIHJldHVybiBgd2luZG93YCBhcyB0aGUgYG9mZnNldFBhcmVudGAuXG4gICAgaWYgKCFjbGllbnRTaXplIHx8ICEoYXdhaXQgKHBsYXRmb3JtLmlzRWxlbWVudCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNFbGVtZW50KGFycm93T2Zmc2V0UGFyZW50KSkpKSB7XG4gICAgICBjbGllbnRTaXplID0gZWxlbWVudHMuZmxvYXRpbmdbY2xpZW50UHJvcF0gfHwgcmVjdHMuZmxvYXRpbmdbbGVuZ3RoXTtcbiAgICB9XG4gICAgY29uc3QgY2VudGVyVG9SZWZlcmVuY2UgPSBlbmREaWZmIC8gMiAtIHN0YXJ0RGlmZiAvIDI7XG5cbiAgICAvLyBJZiB0aGUgcGFkZGluZyBpcyBsYXJnZSBlbm91Z2ggdGhhdCBpdCBjYXVzZXMgdGhlIGFycm93IHRvIG5vIGxvbmdlciBiZVxuICAgIC8vIGNlbnRlcmVkLCBtb2RpZnkgdGhlIHBhZGRpbmcgc28gdGhhdCBpdCBpcyBjZW50ZXJlZC5cbiAgICBjb25zdCBsYXJnZXN0UG9zc2libGVQYWRkaW5nID0gY2xpZW50U2l6ZSAvIDIgLSBhcnJvd0RpbWVuc2lvbnNbbGVuZ3RoXSAvIDIgLSAxO1xuICAgIGNvbnN0IG1pblBhZGRpbmcgPSBtaW4ocGFkZGluZ09iamVjdFttaW5Qcm9wXSwgbGFyZ2VzdFBvc3NpYmxlUGFkZGluZyk7XG4gICAgY29uc3QgbWF4UGFkZGluZyA9IG1pbihwYWRkaW5nT2JqZWN0W21heFByb3BdLCBsYXJnZXN0UG9zc2libGVQYWRkaW5nKTtcblxuICAgIC8vIE1ha2Ugc3VyZSB0aGUgYXJyb3cgZG9lc24ndCBvdmVyZmxvdyB0aGUgZmxvYXRpbmcgZWxlbWVudCBpZiB0aGUgY2VudGVyXG4gICAgLy8gcG9pbnQgaXMgb3V0c2lkZSB0aGUgZmxvYXRpbmcgZWxlbWVudCdzIGJvdW5kcy5cbiAgICBjb25zdCBtaW4kMSA9IG1pblBhZGRpbmc7XG4gICAgY29uc3QgbWF4ID0gY2xpZW50U2l6ZSAtIGFycm93RGltZW5zaW9uc1tsZW5ndGhdIC0gbWF4UGFkZGluZztcbiAgICBjb25zdCBjZW50ZXIgPSBjbGllbnRTaXplIC8gMiAtIGFycm93RGltZW5zaW9uc1tsZW5ndGhdIC8gMiArIGNlbnRlclRvUmVmZXJlbmNlO1xuICAgIGNvbnN0IG9mZnNldCA9IGNsYW1wKG1pbiQxLCBjZW50ZXIsIG1heCk7XG5cbiAgICAvLyBJZiB0aGUgcmVmZXJlbmNlIGlzIHNtYWxsIGVub3VnaCB0aGF0IHRoZSBhcnJvdydzIHBhZGRpbmcgY2F1c2VzIGl0IHRvXG4gICAgLy8gdG8gcG9pbnQgdG8gbm90aGluZyBmb3IgYW4gYWxpZ25lZCBwbGFjZW1lbnQsIGFkanVzdCB0aGUgb2Zmc2V0IG9mIHRoZVxuICAgIC8vIGZsb2F0aW5nIGVsZW1lbnQgaXRzZWxmLiBUbyBlbnN1cmUgYHNoaWZ0KClgIGNvbnRpbnVlcyB0byB0YWtlIGFjdGlvbixcbiAgICAvLyBhIHNpbmdsZSByZXNldCBpcyBwZXJmb3JtZWQgd2hlbiB0aGlzIGlzIHRydWUuXG4gICAgY29uc3Qgc2hvdWxkQWRkT2Zmc2V0ID0gIW1pZGRsZXdhcmVEYXRhLmFycm93ICYmIGdldEFsaWdubWVudChwbGFjZW1lbnQpICE9IG51bGwgJiYgY2VudGVyICE9PSBvZmZzZXQgJiYgcmVjdHMucmVmZXJlbmNlW2xlbmd0aF0gLyAyIC0gKGNlbnRlciA8IG1pbiQxID8gbWluUGFkZGluZyA6IG1heFBhZGRpbmcpIC0gYXJyb3dEaW1lbnNpb25zW2xlbmd0aF0gLyAyIDwgMDtcbiAgICBjb25zdCBhbGlnbm1lbnRPZmZzZXQgPSBzaG91bGRBZGRPZmZzZXQgPyBjZW50ZXIgPCBtaW4kMSA/IGNlbnRlciAtIG1pbiQxIDogY2VudGVyIC0gbWF4IDogMDtcbiAgICByZXR1cm4ge1xuICAgICAgW2F4aXNdOiBjb29yZHNbYXhpc10gKyBhbGlnbm1lbnRPZmZzZXQsXG4gICAgICBkYXRhOiB7XG4gICAgICAgIFtheGlzXTogb2Zmc2V0LFxuICAgICAgICBjZW50ZXJPZmZzZXQ6IGNlbnRlciAtIG9mZnNldCAtIGFsaWdubWVudE9mZnNldCxcbiAgICAgICAgLi4uKHNob3VsZEFkZE9mZnNldCAmJiB7XG4gICAgICAgICAgYWxpZ25tZW50T2Zmc2V0XG4gICAgICAgIH0pXG4gICAgICB9LFxuICAgICAgcmVzZXQ6IHNob3VsZEFkZE9mZnNldFxuICAgIH07XG4gIH1cbn0pO1xuXG5mdW5jdGlvbiBnZXRQbGFjZW1lbnRMaXN0KGFsaWdubWVudCwgYXV0b0FsaWdubWVudCwgYWxsb3dlZFBsYWNlbWVudHMpIHtcbiAgY29uc3QgYWxsb3dlZFBsYWNlbWVudHNTb3J0ZWRCeUFsaWdubWVudCA9IGFsaWdubWVudCA/IFsuLi5hbGxvd2VkUGxhY2VtZW50cy5maWx0ZXIocGxhY2VtZW50ID0+IGdldEFsaWdubWVudChwbGFjZW1lbnQpID09PSBhbGlnbm1lbnQpLCAuLi5hbGxvd2VkUGxhY2VtZW50cy5maWx0ZXIocGxhY2VtZW50ID0+IGdldEFsaWdubWVudChwbGFjZW1lbnQpICE9PSBhbGlnbm1lbnQpXSA6IGFsbG93ZWRQbGFjZW1lbnRzLmZpbHRlcihwbGFjZW1lbnQgPT4gZ2V0U2lkZShwbGFjZW1lbnQpID09PSBwbGFjZW1lbnQpO1xuICByZXR1cm4gYWxsb3dlZFBsYWNlbWVudHNTb3J0ZWRCeUFsaWdubWVudC5maWx0ZXIocGxhY2VtZW50ID0+IHtcbiAgICBpZiAoYWxpZ25tZW50KSB7XG4gICAgICByZXR1cm4gZ2V0QWxpZ25tZW50KHBsYWNlbWVudCkgPT09IGFsaWdubWVudCB8fCAoYXV0b0FsaWdubWVudCA/IGdldE9wcG9zaXRlQWxpZ25tZW50UGxhY2VtZW50KHBsYWNlbWVudCkgIT09IHBsYWNlbWVudCA6IGZhbHNlKTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG4gIH0pO1xufVxuLyoqXG4gKiBPcHRpbWl6ZXMgdGhlIHZpc2liaWxpdHkgb2YgdGhlIGZsb2F0aW5nIGVsZW1lbnQgYnkgY2hvb3NpbmcgdGhlIHBsYWNlbWVudFxuICogdGhhdCBoYXMgdGhlIG1vc3Qgc3BhY2UgYXZhaWxhYmxlIGF1dG9tYXRpY2FsbHksIHdpdGhvdXQgbmVlZGluZyB0byBzcGVjaWZ5IGFcbiAqIHByZWZlcnJlZCBwbGFjZW1lbnQuIEFsdGVybmF0aXZlIHRvIGBmbGlwYC5cbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9hdXRvUGxhY2VtZW50XG4gKi9cbmNvbnN0IGF1dG9QbGFjZW1lbnQgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICBpZiAob3B0aW9ucyA9PT0gdm9pZCAwKSB7XG4gICAgb3B0aW9ucyA9IHt9O1xuICB9XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ2F1dG9QbGFjZW1lbnQnLFxuICAgIG9wdGlvbnMsXG4gICAgYXN5bmMgZm4oc3RhdGUpIHtcbiAgICAgIHZhciBfbWlkZGxld2FyZURhdGEkYXV0b1AsIF9taWRkbGV3YXJlRGF0YSRhdXRvUDIsIF9wbGFjZW1lbnRzVGhhdEZpdE9uRTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcmVjdHMsXG4gICAgICAgIG1pZGRsZXdhcmVEYXRhLFxuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIHBsYXRmb3JtLFxuICAgICAgICBlbGVtZW50c1xuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBjcm9zc0F4aXMgPSBmYWxzZSxcbiAgICAgICAgYWxpZ25tZW50LFxuICAgICAgICBhbGxvd2VkUGxhY2VtZW50cyA9IHBsYWNlbWVudHMsXG4gICAgICAgIGF1dG9BbGlnbm1lbnQgPSB0cnVlLFxuICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnNcbiAgICAgIH0gPSBldmFsdWF0ZShvcHRpb25zLCBzdGF0ZSk7XG4gICAgICBjb25zdCBwbGFjZW1lbnRzJDEgPSBhbGlnbm1lbnQgIT09IHVuZGVmaW5lZCB8fCBhbGxvd2VkUGxhY2VtZW50cyA9PT0gcGxhY2VtZW50cyA/IGdldFBsYWNlbWVudExpc3QoYWxpZ25tZW50IHx8IG51bGwsIGF1dG9BbGlnbm1lbnQsIGFsbG93ZWRQbGFjZW1lbnRzKSA6IGFsbG93ZWRQbGFjZW1lbnRzO1xuICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwgZGV0ZWN0T3ZlcmZsb3dPcHRpb25zKTtcbiAgICAgIGNvbnN0IGN1cnJlbnRJbmRleCA9ICgoX21pZGRsZXdhcmVEYXRhJGF1dG9QID0gbWlkZGxld2FyZURhdGEuYXV0b1BsYWNlbWVudCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9taWRkbGV3YXJlRGF0YSRhdXRvUC5pbmRleCkgfHwgMDtcbiAgICAgIGNvbnN0IGN1cnJlbnRQbGFjZW1lbnQgPSBwbGFjZW1lbnRzJDFbY3VycmVudEluZGV4XTtcbiAgICAgIGlmIChjdXJyZW50UGxhY2VtZW50ID09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgfVxuICAgICAgY29uc3QgYWxpZ25tZW50U2lkZXMgPSBnZXRBbGlnbm1lbnRTaWRlcyhjdXJyZW50UGxhY2VtZW50LCByZWN0cywgYXdhaXQgKHBsYXRmb3JtLmlzUlRMID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5pc1JUTChlbGVtZW50cy5mbG9hdGluZykpKTtcblxuICAgICAgLy8gTWFrZSBgY29tcHV0ZUNvb3Jkc2Agc3RhcnQgZnJvbSB0aGUgcmlnaHQgcGxhY2UuXG4gICAgICBpZiAocGxhY2VtZW50ICE9PSBjdXJyZW50UGxhY2VtZW50KSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgcmVzZXQ6IHtcbiAgICAgICAgICAgIHBsYWNlbWVudDogcGxhY2VtZW50cyQxWzBdXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgY29uc3QgY3VycmVudE92ZXJmbG93cyA9IFtvdmVyZmxvd1tnZXRTaWRlKGN1cnJlbnRQbGFjZW1lbnQpXSwgb3ZlcmZsb3dbYWxpZ25tZW50U2lkZXNbMF1dLCBvdmVyZmxvd1thbGlnbm1lbnRTaWRlc1sxXV1dO1xuICAgICAgY29uc3QgYWxsT3ZlcmZsb3dzID0gWy4uLigoKF9taWRkbGV3YXJlRGF0YSRhdXRvUDIgPSBtaWRkbGV3YXJlRGF0YS5hdXRvUGxhY2VtZW50KSA9PSBudWxsID8gdm9pZCAwIDogX21pZGRsZXdhcmVEYXRhJGF1dG9QMi5vdmVyZmxvd3MpIHx8IFtdKSwge1xuICAgICAgICBwbGFjZW1lbnQ6IGN1cnJlbnRQbGFjZW1lbnQsXG4gICAgICAgIG92ZXJmbG93czogY3VycmVudE92ZXJmbG93c1xuICAgICAgfV07XG4gICAgICBjb25zdCBuZXh0UGxhY2VtZW50ID0gcGxhY2VtZW50cyQxW2N1cnJlbnRJbmRleCArIDFdO1xuXG4gICAgICAvLyBUaGVyZSBhcmUgbW9yZSBwbGFjZW1lbnRzIHRvIGNoZWNrLlxuICAgICAgaWYgKG5leHRQbGFjZW1lbnQpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICBpbmRleDogY3VycmVudEluZGV4ICsgMSxcbiAgICAgICAgICAgIG92ZXJmbG93czogYWxsT3ZlcmZsb3dzXG4gICAgICAgICAgfSxcbiAgICAgICAgICByZXNldDoge1xuICAgICAgICAgICAgcGxhY2VtZW50OiBuZXh0UGxhY2VtZW50XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgY29uc3QgcGxhY2VtZW50c1NvcnRlZEJ5TW9zdFNwYWNlID0gYWxsT3ZlcmZsb3dzLm1hcChkID0+IHtcbiAgICAgICAgY29uc3QgYWxpZ25tZW50ID0gZ2V0QWxpZ25tZW50KGQucGxhY2VtZW50KTtcbiAgICAgICAgcmV0dXJuIFtkLnBsYWNlbWVudCwgYWxpZ25tZW50ICYmIGNyb3NzQXhpcyA/XG4gICAgICAgIC8vIENoZWNrIGFsb25nIHRoZSBtYWluQXhpcyBhbmQgbWFpbiBjcm9zc0F4aXMgc2lkZS5cbiAgICAgICAgZC5vdmVyZmxvd3Muc2xpY2UoMCwgMikucmVkdWNlKChhY2MsIHYpID0+IGFjYyArIHYsIDApIDpcbiAgICAgICAgLy8gQ2hlY2sgb25seSB0aGUgbWFpbkF4aXMuXG4gICAgICAgIGQub3ZlcmZsb3dzWzBdLCBkLm92ZXJmbG93c107XG4gICAgICB9KS5zb3J0KChhLCBiKSA9PiBhWzFdIC0gYlsxXSk7XG4gICAgICBjb25zdCBwbGFjZW1lbnRzVGhhdEZpdE9uRWFjaFNpZGUgPSBwbGFjZW1lbnRzU29ydGVkQnlNb3N0U3BhY2UuZmlsdGVyKGQgPT4gZFsyXS5zbGljZSgwLFxuICAgICAgLy8gQWxpZ25lZCBwbGFjZW1lbnRzIHNob3VsZCBub3QgY2hlY2sgdGhlaXIgb3Bwb3NpdGUgY3Jvc3NBeGlzXG4gICAgICAvLyBzaWRlLlxuICAgICAgZ2V0QWxpZ25tZW50KGRbMF0pID8gMiA6IDMpLmV2ZXJ5KHYgPT4gdiA8PSAwKSk7XG4gICAgICBjb25zdCByZXNldFBsYWNlbWVudCA9ICgoX3BsYWNlbWVudHNUaGF0Rml0T25FID0gcGxhY2VtZW50c1RoYXRGaXRPbkVhY2hTaWRlWzBdKSA9PSBudWxsID8gdm9pZCAwIDogX3BsYWNlbWVudHNUaGF0Rml0T25FWzBdKSB8fCBwbGFjZW1lbnRzU29ydGVkQnlNb3N0U3BhY2VbMF1bMF07XG4gICAgICBpZiAocmVzZXRQbGFjZW1lbnQgIT09IHBsYWNlbWVudCkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgIGluZGV4OiBjdXJyZW50SW5kZXggKyAxLFxuICAgICAgICAgICAgb3ZlcmZsb3dzOiBhbGxPdmVyZmxvd3NcbiAgICAgICAgICB9LFxuICAgICAgICAgIHJlc2V0OiB7XG4gICAgICAgICAgICBwbGFjZW1lbnQ6IHJlc2V0UGxhY2VtZW50XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgcmV0dXJuIHt9O1xuICAgIH1cbiAgfTtcbn07XG5cbi8qKlxuICogT3B0aW1pemVzIHRoZSB2aXNpYmlsaXR5IG9mIHRoZSBmbG9hdGluZyBlbGVtZW50IGJ5IGZsaXBwaW5nIHRoZSBgcGxhY2VtZW50YFxuICogaW4gb3JkZXIgdG8ga2VlcCBpdCBpbiB2aWV3IHdoZW4gdGhlIHByZWZlcnJlZCBwbGFjZW1lbnQocykgd2lsbCBvdmVyZmxvdyB0aGVcbiAqIGNsaXBwaW5nIGJvdW5kYXJ5LiBBbHRlcm5hdGl2ZSB0byBgYXV0b1BsYWNlbWVudGAuXG4gKiBAc2VlIGh0dHBzOi8vZmxvYXRpbmctdWkuY29tL2RvY3MvZmxpcFxuICovXG5jb25zdCBmbGlwID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG5hbWU6ICdmbGlwJyxcbiAgICBvcHRpb25zLFxuICAgIGFzeW5jIGZuKHN0YXRlKSB7XG4gICAgICB2YXIgX21pZGRsZXdhcmVEYXRhJGFycm93LCBfbWlkZGxld2FyZURhdGEkZmxpcDtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcGxhY2VtZW50LFxuICAgICAgICBtaWRkbGV3YXJlRGF0YSxcbiAgICAgICAgcmVjdHMsXG4gICAgICAgIGluaXRpYWxQbGFjZW1lbnQsXG4gICAgICAgIHBsYXRmb3JtLFxuICAgICAgICBlbGVtZW50c1xuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBtYWluQXhpczogY2hlY2tNYWluQXhpcyA9IHRydWUsXG4gICAgICAgIGNyb3NzQXhpczogY2hlY2tDcm9zc0F4aXMgPSB0cnVlLFxuICAgICAgICBmYWxsYmFja1BsYWNlbWVudHM6IHNwZWNpZmllZEZhbGxiYWNrUGxhY2VtZW50cyxcbiAgICAgICAgZmFsbGJhY2tTdHJhdGVneSA9ICdiZXN0Rml0JyxcbiAgICAgICAgZmFsbGJhY2tBeGlzU2lkZURpcmVjdGlvbiA9ICdub25lJyxcbiAgICAgICAgZmxpcEFsaWdubWVudCA9IHRydWUsXG4gICAgICAgIC4uLmRldGVjdE92ZXJmbG93T3B0aW9uc1xuICAgICAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKTtcblxuICAgICAgLy8gSWYgYSByZXNldCBieSB0aGUgYXJyb3cgd2FzIGNhdXNlZCBkdWUgdG8gYW4gYWxpZ25tZW50IG9mZnNldCBiZWluZ1xuICAgICAgLy8gYWRkZWQsIHdlIHNob3VsZCBza2lwIGFueSBsb2dpYyBub3cgc2luY2UgYGZsaXAoKWAgaGFzIGFscmVhZHkgZG9uZSBpdHNcbiAgICAgIC8vIHdvcmsuXG4gICAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vZmxvYXRpbmctdWkvZmxvYXRpbmctdWkvaXNzdWVzLzI1NDkjaXNzdWVjb21tZW50LTE3MTk2MDE2NDNcbiAgICAgIGlmICgoX21pZGRsZXdhcmVEYXRhJGFycm93ID0gbWlkZGxld2FyZURhdGEuYXJyb3cpICE9IG51bGwgJiYgX21pZGRsZXdhcmVEYXRhJGFycm93LmFsaWdubWVudE9mZnNldCkge1xuICAgICAgICByZXR1cm4ge307XG4gICAgICB9XG4gICAgICBjb25zdCBzaWRlID0gZ2V0U2lkZShwbGFjZW1lbnQpO1xuICAgICAgY29uc3QgaW5pdGlhbFNpZGVBeGlzID0gZ2V0U2lkZUF4aXMoaW5pdGlhbFBsYWNlbWVudCk7XG4gICAgICBjb25zdCBpc0Jhc2VQbGFjZW1lbnQgPSBnZXRTaWRlKGluaXRpYWxQbGFjZW1lbnQpID09PSBpbml0aWFsUGxhY2VtZW50O1xuICAgICAgY29uc3QgcnRsID0gYXdhaXQgKHBsYXRmb3JtLmlzUlRMID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5pc1JUTChlbGVtZW50cy5mbG9hdGluZykpO1xuICAgICAgY29uc3QgZmFsbGJhY2tQbGFjZW1lbnRzID0gc3BlY2lmaWVkRmFsbGJhY2tQbGFjZW1lbnRzIHx8IChpc0Jhc2VQbGFjZW1lbnQgfHwgIWZsaXBBbGlnbm1lbnQgPyBbZ2V0T3Bwb3NpdGVQbGFjZW1lbnQoaW5pdGlhbFBsYWNlbWVudCldIDogZ2V0RXhwYW5kZWRQbGFjZW1lbnRzKGluaXRpYWxQbGFjZW1lbnQpKTtcbiAgICAgIGNvbnN0IGhhc0ZhbGxiYWNrQXhpc1NpZGVEaXJlY3Rpb24gPSBmYWxsYmFja0F4aXNTaWRlRGlyZWN0aW9uICE9PSAnbm9uZSc7XG4gICAgICBpZiAoIXNwZWNpZmllZEZhbGxiYWNrUGxhY2VtZW50cyAmJiBoYXNGYWxsYmFja0F4aXNTaWRlRGlyZWN0aW9uKSB7XG4gICAgICAgIGZhbGxiYWNrUGxhY2VtZW50cy5wdXNoKC4uLmdldE9wcG9zaXRlQXhpc1BsYWNlbWVudHMoaW5pdGlhbFBsYWNlbWVudCwgZmxpcEFsaWdubWVudCwgZmFsbGJhY2tBeGlzU2lkZURpcmVjdGlvbiwgcnRsKSk7XG4gICAgICB9XG4gICAgICBjb25zdCBwbGFjZW1lbnRzID0gW2luaXRpYWxQbGFjZW1lbnQsIC4uLmZhbGxiYWNrUGxhY2VtZW50c107XG4gICAgICBjb25zdCBvdmVyZmxvdyA9IGF3YWl0IGRldGVjdE92ZXJmbG93KHN0YXRlLCBkZXRlY3RPdmVyZmxvd09wdGlvbnMpO1xuICAgICAgY29uc3Qgb3ZlcmZsb3dzID0gW107XG4gICAgICBsZXQgb3ZlcmZsb3dzRGF0YSA9ICgoX21pZGRsZXdhcmVEYXRhJGZsaXAgPSBtaWRkbGV3YXJlRGF0YS5mbGlwKSA9PSBudWxsID8gdm9pZCAwIDogX21pZGRsZXdhcmVEYXRhJGZsaXAub3ZlcmZsb3dzKSB8fCBbXTtcbiAgICAgIGlmIChjaGVja01haW5BeGlzKSB7XG4gICAgICAgIG92ZXJmbG93cy5wdXNoKG92ZXJmbG93W3NpZGVdKTtcbiAgICAgIH1cbiAgICAgIGlmIChjaGVja0Nyb3NzQXhpcykge1xuICAgICAgICBjb25zdCBzaWRlcyA9IGdldEFsaWdubWVudFNpZGVzKHBsYWNlbWVudCwgcmVjdHMsIHJ0bCk7XG4gICAgICAgIG92ZXJmbG93cy5wdXNoKG92ZXJmbG93W3NpZGVzWzBdXSwgb3ZlcmZsb3dbc2lkZXNbMV1dKTtcbiAgICAgIH1cbiAgICAgIG92ZXJmbG93c0RhdGEgPSBbLi4ub3ZlcmZsb3dzRGF0YSwge1xuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIG92ZXJmbG93c1xuICAgICAgfV07XG5cbiAgICAgIC8vIE9uZSBvciBtb3JlIHNpZGVzIGlzIG92ZXJmbG93aW5nLlxuICAgICAgaWYgKCFvdmVyZmxvd3MuZXZlcnkoc2lkZSA9PiBzaWRlIDw9IDApKSB7XG4gICAgICAgIHZhciBfbWlkZGxld2FyZURhdGEkZmxpcDIsIF9vdmVyZmxvd3NEYXRhJGZpbHRlcjtcbiAgICAgICAgY29uc3QgbmV4dEluZGV4ID0gKCgoX21pZGRsZXdhcmVEYXRhJGZsaXAyID0gbWlkZGxld2FyZURhdGEuZmxpcCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9taWRkbGV3YXJlRGF0YSRmbGlwMi5pbmRleCkgfHwgMCkgKyAxO1xuICAgICAgICBjb25zdCBuZXh0UGxhY2VtZW50ID0gcGxhY2VtZW50c1tuZXh0SW5kZXhdO1xuICAgICAgICBpZiAobmV4dFBsYWNlbWVudCkge1xuICAgICAgICAgIGNvbnN0IGlnbm9yZUNyb3NzQXhpc092ZXJmbG93ID0gY2hlY2tDcm9zc0F4aXMgPT09ICdhbGlnbm1lbnQnID8gaW5pdGlhbFNpZGVBeGlzICE9PSBnZXRTaWRlQXhpcyhuZXh0UGxhY2VtZW50KSA6IGZhbHNlO1xuICAgICAgICAgIGlmICghaWdub3JlQ3Jvc3NBeGlzT3ZlcmZsb3cgfHxcbiAgICAgICAgICAvLyBXZSBsZWF2ZSB0aGUgY3VycmVudCBtYWluIGF4aXMgb25seSBpZiBldmVyeSBwbGFjZW1lbnQgb24gdGhhdCBheGlzXG4gICAgICAgICAgLy8gb3ZlcmZsb3dzIHRoZSBtYWluIGF4aXMuXG4gICAgICAgICAgb3ZlcmZsb3dzRGF0YS5ldmVyeShkID0+IGQub3ZlcmZsb3dzWzBdID4gMCAmJiBnZXRTaWRlQXhpcyhkLnBsYWNlbWVudCkgPT09IGluaXRpYWxTaWRlQXhpcykpIHtcbiAgICAgICAgICAgIC8vIFRyeSBuZXh0IHBsYWNlbWVudCBhbmQgcmUtcnVuIHRoZSBsaWZlY3ljbGUuXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAgICAgaW5kZXg6IG5leHRJbmRleCxcbiAgICAgICAgICAgICAgICBvdmVyZmxvd3M6IG92ZXJmbG93c0RhdGFcbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgcmVzZXQ6IHtcbiAgICAgICAgICAgICAgICBwbGFjZW1lbnQ6IG5leHRQbGFjZW1lbnRcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBGaXJzdCwgZmluZCB0aGUgY2FuZGlkYXRlcyB0aGF0IGZpdCBvbiB0aGUgbWFpbkF4aXMgc2lkZSBvZiBvdmVyZmxvdyxcbiAgICAgICAgLy8gdGhlbiBmaW5kIHRoZSBwbGFjZW1lbnQgdGhhdCBmaXRzIHRoZSBiZXN0IG9uIHRoZSBtYWluIGNyb3NzQXhpcyBzaWRlLlxuICAgICAgICBsZXQgcmVzZXRQbGFjZW1lbnQgPSAoX292ZXJmbG93c0RhdGEkZmlsdGVyID0gb3ZlcmZsb3dzRGF0YS5maWx0ZXIoZCA9PiBkLm92ZXJmbG93c1swXSA8PSAwKS5zb3J0KChhLCBiKSA9PiBhLm92ZXJmbG93c1sxXSAtIGIub3ZlcmZsb3dzWzFdKVswXSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9vdmVyZmxvd3NEYXRhJGZpbHRlci5wbGFjZW1lbnQ7XG5cbiAgICAgICAgLy8gT3RoZXJ3aXNlIGZhbGxiYWNrLlxuICAgICAgICBpZiAoIXJlc2V0UGxhY2VtZW50KSB7XG4gICAgICAgICAgc3dpdGNoIChmYWxsYmFja1N0cmF0ZWd5KSB7XG4gICAgICAgICAgICBjYXNlICdiZXN0Rml0JzpcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHZhciBfb3ZlcmZsb3dzRGF0YSRmaWx0ZXIyO1xuICAgICAgICAgICAgICAgIGNvbnN0IHBsYWNlbWVudCA9IChfb3ZlcmZsb3dzRGF0YSRmaWx0ZXIyID0gb3ZlcmZsb3dzRGF0YS5maWx0ZXIoZCA9PiB7XG4gICAgICAgICAgICAgICAgICBpZiAoaGFzRmFsbGJhY2tBeGlzU2lkZURpcmVjdGlvbikge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBjdXJyZW50U2lkZUF4aXMgPSBnZXRTaWRlQXhpcyhkLnBsYWNlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBjdXJyZW50U2lkZUF4aXMgPT09IGluaXRpYWxTaWRlQXhpcyB8fFxuICAgICAgICAgICAgICAgICAgICAvLyBDcmVhdGUgYSBiaWFzIHRvIHRoZSBgeWAgc2lkZSBheGlzIGR1ZSB0byBob3Jpem9udGFsXG4gICAgICAgICAgICAgICAgICAgIC8vIHJlYWRpbmcgZGlyZWN0aW9ucyBmYXZvcmluZyBncmVhdGVyIHdpZHRoLlxuICAgICAgICAgICAgICAgICAgICBjdXJyZW50U2lkZUF4aXMgPT09ICd5JztcbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICAgICAgICAgIH0pLm1hcChkID0+IFtkLnBsYWNlbWVudCwgZC5vdmVyZmxvd3MuZmlsdGVyKG92ZXJmbG93ID0+IG92ZXJmbG93ID4gMCkucmVkdWNlKChhY2MsIG92ZXJmbG93KSA9PiBhY2MgKyBvdmVyZmxvdywgMCldKS5zb3J0KChhLCBiKSA9PiBhWzFdIC0gYlsxXSlbMF0pID09IG51bGwgPyB2b2lkIDAgOiBfb3ZlcmZsb3dzRGF0YSRmaWx0ZXIyWzBdO1xuICAgICAgICAgICAgICAgIGlmIChwbGFjZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgIHJlc2V0UGxhY2VtZW50ID0gcGxhY2VtZW50O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2FzZSAnaW5pdGlhbFBsYWNlbWVudCc6XG4gICAgICAgICAgICAgIHJlc2V0UGxhY2VtZW50ID0gaW5pdGlhbFBsYWNlbWVudDtcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmIChwbGFjZW1lbnQgIT09IHJlc2V0UGxhY2VtZW50KSB7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHJlc2V0OiB7XG4gICAgICAgICAgICAgIHBsYWNlbWVudDogcmVzZXRQbGFjZW1lbnRcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuICB9O1xufTtcblxuZnVuY3Rpb24gZ2V0U2lkZU9mZnNldHMob3ZlcmZsb3csIHJlY3QpIHtcbiAgcmV0dXJuIHtcbiAgICB0b3A6IG92ZXJmbG93LnRvcCAtIHJlY3QuaGVpZ2h0LFxuICAgIHJpZ2h0OiBvdmVyZmxvdy5yaWdodCAtIHJlY3Qud2lkdGgsXG4gICAgYm90dG9tOiBvdmVyZmxvdy5ib3R0b20gLSByZWN0LmhlaWdodCxcbiAgICBsZWZ0OiBvdmVyZmxvdy5sZWZ0IC0gcmVjdC53aWR0aFxuICB9O1xufVxuZnVuY3Rpb24gaXNBbnlTaWRlRnVsbHlDbGlwcGVkKG92ZXJmbG93KSB7XG4gIHJldHVybiBzaWRlcy5zb21lKHNpZGUgPT4gb3ZlcmZsb3dbc2lkZV0gPj0gMCk7XG59XG4vKipcbiAqIFByb3ZpZGVzIGRhdGEgdG8gaGlkZSB0aGUgZmxvYXRpbmcgZWxlbWVudCBpbiBhcHBsaWNhYmxlIHNpdHVhdGlvbnMsIHN1Y2ggYXNcbiAqIHdoZW4gaXQgaXMgbm90IGluIHRoZSBzYW1lIGNsaXBwaW5nIGNvbnRleHQgYXMgdGhlIHJlZmVyZW5jZSBlbGVtZW50LlxuICogQHNlZSBodHRwczovL2Zsb2F0aW5nLXVpLmNvbS9kb2NzL2hpZGVcbiAqL1xuY29uc3QgaGlkZSA9IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gIGlmIChvcHRpb25zID09PSB2b2lkIDApIHtcbiAgICBvcHRpb25zID0ge307XG4gIH1cbiAgcmV0dXJuIHtcbiAgICBuYW1lOiAnaGlkZScsXG4gICAgb3B0aW9ucyxcbiAgICBhc3luYyBmbihzdGF0ZSkge1xuICAgICAgY29uc3Qge1xuICAgICAgICByZWN0c1xuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBzdHJhdGVneSA9ICdyZWZlcmVuY2VIaWRkZW4nLFxuICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnNcbiAgICAgIH0gPSBldmFsdWF0ZShvcHRpb25zLCBzdGF0ZSk7XG4gICAgICBzd2l0Y2ggKHN0cmF0ZWd5KSB7XG4gICAgICAgIGNhc2UgJ3JlZmVyZW5jZUhpZGRlbic6XG4gICAgICAgICAge1xuICAgICAgICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwge1xuICAgICAgICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnMsXG4gICAgICAgICAgICAgIGVsZW1lbnRDb250ZXh0OiAncmVmZXJlbmNlJ1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBjb25zdCBvZmZzZXRzID0gZ2V0U2lkZU9mZnNldHMob3ZlcmZsb3csIHJlY3RzLnJlZmVyZW5jZSk7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAgICAgcmVmZXJlbmNlSGlkZGVuT2Zmc2V0czogb2Zmc2V0cyxcbiAgICAgICAgICAgICAgICByZWZlcmVuY2VIaWRkZW46IGlzQW55U2lkZUZ1bGx5Q2xpcHBlZChvZmZzZXRzKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgY2FzZSAnZXNjYXBlZCc6XG4gICAgICAgICAge1xuICAgICAgICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwge1xuICAgICAgICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnMsXG4gICAgICAgICAgICAgIGFsdEJvdW5kYXJ5OiB0cnVlXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGNvbnN0IG9mZnNldHMgPSBnZXRTaWRlT2Zmc2V0cyhvdmVyZmxvdywgcmVjdHMuZmxvYXRpbmcpO1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgZGF0YToge1xuICAgICAgICAgICAgICAgIGVzY2FwZWRPZmZzZXRzOiBvZmZzZXRzLFxuICAgICAgICAgICAgICAgIGVzY2FwZWQ6IGlzQW55U2lkZUZ1bGx5Q2xpcHBlZChvZmZzZXRzKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICB7XG4gICAgICAgICAgICByZXR1cm4ge307XG4gICAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfTtcbn07XG5cbmZ1bmN0aW9uIGdldEJvdW5kaW5nUmVjdChyZWN0cykge1xuICBjb25zdCBtaW5YID0gbWluKC4uLnJlY3RzLm1hcChyZWN0ID0+IHJlY3QubGVmdCkpO1xuICBjb25zdCBtaW5ZID0gbWluKC4uLnJlY3RzLm1hcChyZWN0ID0+IHJlY3QudG9wKSk7XG4gIGNvbnN0IG1heFggPSBtYXgoLi4ucmVjdHMubWFwKHJlY3QgPT4gcmVjdC5yaWdodCkpO1xuICBjb25zdCBtYXhZID0gbWF4KC4uLnJlY3RzLm1hcChyZWN0ID0+IHJlY3QuYm90dG9tKSk7XG4gIHJldHVybiB7XG4gICAgeDogbWluWCxcbiAgICB5OiBtaW5ZLFxuICAgIHdpZHRoOiBtYXhYIC0gbWluWCxcbiAgICBoZWlnaHQ6IG1heFkgLSBtaW5ZXG4gIH07XG59XG5mdW5jdGlvbiBnZXRSZWN0c0J5TGluZShyZWN0cykge1xuICBjb25zdCBzb3J0ZWRSZWN0cyA9IHJlY3RzLnNsaWNlKCkuc29ydCgoYSwgYikgPT4gYS55IC0gYi55KTtcbiAgY29uc3QgZ3JvdXBzID0gW107XG4gIGxldCBwcmV2UmVjdCA9IG51bGw7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgc29ydGVkUmVjdHMubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCByZWN0ID0gc29ydGVkUmVjdHNbaV07XG4gICAgaWYgKCFwcmV2UmVjdCB8fCByZWN0LnkgLSBwcmV2UmVjdC55ID4gcHJldlJlY3QuaGVpZ2h0IC8gMikge1xuICAgICAgZ3JvdXBzLnB1c2goW3JlY3RdKTtcbiAgICB9IGVsc2Uge1xuICAgICAgZ3JvdXBzW2dyb3Vwcy5sZW5ndGggLSAxXS5wdXNoKHJlY3QpO1xuICAgIH1cbiAgICBwcmV2UmVjdCA9IHJlY3Q7XG4gIH1cbiAgcmV0dXJuIGdyb3Vwcy5tYXAocmVjdCA9PiByZWN0VG9DbGllbnRSZWN0KGdldEJvdW5kaW5nUmVjdChyZWN0KSkpO1xufVxuLyoqXG4gKiBQcm92aWRlcyBpbXByb3ZlZCBwb3NpdGlvbmluZyBmb3IgaW5saW5lIHJlZmVyZW5jZSBlbGVtZW50cyB0aGF0IGNhbiBzcGFuXG4gKiBvdmVyIG11bHRpcGxlIGxpbmVzLCBzdWNoIGFzIGh5cGVybGlua3Mgb3IgcmFuZ2Ugc2VsZWN0aW9ucy5cbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9pbmxpbmVcbiAqL1xuY29uc3QgaW5saW5lID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG5hbWU6ICdpbmxpbmUnLFxuICAgIG9wdGlvbnMsXG4gICAgYXN5bmMgZm4oc3RhdGUpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcGxhY2VtZW50LFxuICAgICAgICBlbGVtZW50cyxcbiAgICAgICAgcmVjdHMsXG4gICAgICAgIHBsYXRmb3JtLFxuICAgICAgICBzdHJhdGVneVxuICAgICAgfSA9IHN0YXRlO1xuICAgICAgLy8gQSBNb3VzZUV2ZW50J3MgY2xpZW50e1gsWX0gY29vcmRzIGNhbiBiZSB1cCB0byAyIHBpeGVscyBvZmYgYVxuICAgICAgLy8gQ2xpZW50UmVjdCdzIGJvdW5kcywgZGVzcGl0ZSB0aGUgZXZlbnQgbGlzdGVuZXIgYmVpbmcgdHJpZ2dlcmVkLiBBXG4gICAgICAvLyBwYWRkaW5nIG9mIDIgc2VlbXMgdG8gaGFuZGxlIHRoaXMgaXNzdWUuXG4gICAgICBjb25zdCB7XG4gICAgICAgIHBhZGRpbmcgPSAyLFxuICAgICAgICB4LFxuICAgICAgICB5XG4gICAgICB9ID0gZXZhbHVhdGUob3B0aW9ucywgc3RhdGUpO1xuICAgICAgY29uc3QgbmF0aXZlQ2xpZW50UmVjdHMgPSBBcnJheS5mcm9tKChhd2FpdCAocGxhdGZvcm0uZ2V0Q2xpZW50UmVjdHMgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmdldENsaWVudFJlY3RzKGVsZW1lbnRzLnJlZmVyZW5jZSkpKSB8fCBbXSk7XG4gICAgICBjb25zdCBjbGllbnRSZWN0cyA9IGdldFJlY3RzQnlMaW5lKG5hdGl2ZUNsaWVudFJlY3RzKTtcbiAgICAgIGNvbnN0IGZhbGxiYWNrID0gcmVjdFRvQ2xpZW50UmVjdChnZXRCb3VuZGluZ1JlY3QobmF0aXZlQ2xpZW50UmVjdHMpKTtcbiAgICAgIGNvbnN0IHBhZGRpbmdPYmplY3QgPSBnZXRQYWRkaW5nT2JqZWN0KHBhZGRpbmcpO1xuICAgICAgZnVuY3Rpb24gZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkge1xuICAgICAgICAvLyBUaGVyZSBhcmUgdHdvIHJlY3RzIGFuZCB0aGV5IGFyZSBkaXNqb2luZWQuXG4gICAgICAgIGlmIChjbGllbnRSZWN0cy5sZW5ndGggPT09IDIgJiYgY2xpZW50UmVjdHNbMF0ubGVmdCA+IGNsaWVudFJlY3RzWzFdLnJpZ2h0ICYmIHggIT0gbnVsbCAmJiB5ICE9IG51bGwpIHtcbiAgICAgICAgICAvLyBGaW5kIHRoZSBmaXJzdCByZWN0IGluIHdoaWNoIHRoZSBwb2ludCBpcyBmdWxseSBpbnNpZGUuXG4gICAgICAgICAgcmV0dXJuIGNsaWVudFJlY3RzLmZpbmQocmVjdCA9PiB4ID4gcmVjdC5sZWZ0IC0gcGFkZGluZ09iamVjdC5sZWZ0ICYmIHggPCByZWN0LnJpZ2h0ICsgcGFkZGluZ09iamVjdC5yaWdodCAmJiB5ID4gcmVjdC50b3AgLSBwYWRkaW5nT2JqZWN0LnRvcCAmJiB5IDwgcmVjdC5ib3R0b20gKyBwYWRkaW5nT2JqZWN0LmJvdHRvbSkgfHwgZmFsbGJhY2s7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUaGVyZSBhcmUgMiBvciBtb3JlIGNvbm5lY3RlZCByZWN0cy5cbiAgICAgICAgaWYgKGNsaWVudFJlY3RzLmxlbmd0aCA+PSAyKSB7XG4gICAgICAgICAgaWYgKGdldFNpZGVBeGlzKHBsYWNlbWVudCkgPT09ICd5Jykge1xuICAgICAgICAgICAgY29uc3QgZmlyc3RSZWN0ID0gY2xpZW50UmVjdHNbMF07XG4gICAgICAgICAgICBjb25zdCBsYXN0UmVjdCA9IGNsaWVudFJlY3RzW2NsaWVudFJlY3RzLmxlbmd0aCAtIDFdO1xuICAgICAgICAgICAgY29uc3QgaXNUb3AgPSBnZXRTaWRlKHBsYWNlbWVudCkgPT09ICd0b3AnO1xuICAgICAgICAgICAgY29uc3QgdG9wID0gZmlyc3RSZWN0LnRvcDtcbiAgICAgICAgICAgIGNvbnN0IGJvdHRvbSA9IGxhc3RSZWN0LmJvdHRvbTtcbiAgICAgICAgICAgIGNvbnN0IGxlZnQgPSBpc1RvcCA/IGZpcnN0UmVjdC5sZWZ0IDogbGFzdFJlY3QubGVmdDtcbiAgICAgICAgICAgIGNvbnN0IHJpZ2h0ID0gaXNUb3AgPyBmaXJzdFJlY3QucmlnaHQgOiBsYXN0UmVjdC5yaWdodDtcbiAgICAgICAgICAgIGNvbnN0IHdpZHRoID0gcmlnaHQgLSBsZWZ0O1xuICAgICAgICAgICAgY29uc3QgaGVpZ2h0ID0gYm90dG9tIC0gdG9wO1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgdG9wLFxuICAgICAgICAgICAgICBib3R0b20sXG4gICAgICAgICAgICAgIGxlZnQsXG4gICAgICAgICAgICAgIHJpZ2h0LFxuICAgICAgICAgICAgICB3aWR0aCxcbiAgICAgICAgICAgICAgaGVpZ2h0LFxuICAgICAgICAgICAgICB4OiBsZWZ0LFxuICAgICAgICAgICAgICB5OiB0b3BcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IGlzTGVmdFNpZGUgPSBnZXRTaWRlKHBsYWNlbWVudCkgPT09ICdsZWZ0JztcbiAgICAgICAgICBjb25zdCBtYXhSaWdodCA9IG1heCguLi5jbGllbnRSZWN0cy5tYXAocmVjdCA9PiByZWN0LnJpZ2h0KSk7XG4gICAgICAgICAgY29uc3QgbWluTGVmdCA9IG1pbiguLi5jbGllbnRSZWN0cy5tYXAocmVjdCA9PiByZWN0LmxlZnQpKTtcbiAgICAgICAgICBjb25zdCBtZWFzdXJlUmVjdHMgPSBjbGllbnRSZWN0cy5maWx0ZXIocmVjdCA9PiBpc0xlZnRTaWRlID8gcmVjdC5sZWZ0ID09PSBtaW5MZWZ0IDogcmVjdC5yaWdodCA9PT0gbWF4UmlnaHQpO1xuICAgICAgICAgIGNvbnN0IHRvcCA9IG1lYXN1cmVSZWN0c1swXS50b3A7XG4gICAgICAgICAgY29uc3QgYm90dG9tID0gbWVhc3VyZVJlY3RzW21lYXN1cmVSZWN0cy5sZW5ndGggLSAxXS5ib3R0b207XG4gICAgICAgICAgY29uc3QgbGVmdCA9IG1pbkxlZnQ7XG4gICAgICAgICAgY29uc3QgcmlnaHQgPSBtYXhSaWdodDtcbiAgICAgICAgICBjb25zdCB3aWR0aCA9IHJpZ2h0IC0gbGVmdDtcbiAgICAgICAgICBjb25zdCBoZWlnaHQgPSBib3R0b20gLSB0b3A7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHRvcCxcbiAgICAgICAgICAgIGJvdHRvbSxcbiAgICAgICAgICAgIGxlZnQsXG4gICAgICAgICAgICByaWdodCxcbiAgICAgICAgICAgIHdpZHRoLFxuICAgICAgICAgICAgaGVpZ2h0LFxuICAgICAgICAgICAgeDogbGVmdCxcbiAgICAgICAgICAgIHk6IHRvcFxuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGZhbGxiYWNrO1xuICAgICAgfVxuICAgICAgY29uc3QgcmVzZXRSZWN0cyA9IGF3YWl0IHBsYXRmb3JtLmdldEVsZW1lbnRSZWN0cyh7XG4gICAgICAgIHJlZmVyZW5jZToge1xuICAgICAgICAgIGdldEJvdW5kaW5nQ2xpZW50UmVjdFxuICAgICAgICB9LFxuICAgICAgICBmbG9hdGluZzogZWxlbWVudHMuZmxvYXRpbmcsXG4gICAgICAgIHN0cmF0ZWd5XG4gICAgICB9KTtcbiAgICAgIGlmIChyZWN0cy5yZWZlcmVuY2UueCAhPT0gcmVzZXRSZWN0cy5yZWZlcmVuY2UueCB8fCByZWN0cy5yZWZlcmVuY2UueSAhPT0gcmVzZXRSZWN0cy5yZWZlcmVuY2UueSB8fCByZWN0cy5yZWZlcmVuY2Uud2lkdGggIT09IHJlc2V0UmVjdHMucmVmZXJlbmNlLndpZHRoIHx8IHJlY3RzLnJlZmVyZW5jZS5oZWlnaHQgIT09IHJlc2V0UmVjdHMucmVmZXJlbmNlLmhlaWdodCkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHJlc2V0OiB7XG4gICAgICAgICAgICByZWN0czogcmVzZXRSZWN0c1xuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB7fTtcbiAgICB9XG4gIH07XG59O1xuXG5jb25zdCBvcmlnaW5TaWRlcyA9IC8qI19fUFVSRV9fKi9uZXcgU2V0KFsnbGVmdCcsICd0b3AnXSk7XG5cbi8vIEZvciB0eXBlIGJhY2t3YXJkcy1jb21wYXRpYmlsaXR5LCB0aGUgYE9mZnNldE9wdGlvbnNgIHR5cGUgd2FzIGFsc29cbi8vIERlcml2YWJsZS5cblxuYXN5bmMgZnVuY3Rpb24gY29udmVydFZhbHVlVG9Db29yZHMoc3RhdGUsIG9wdGlvbnMpIHtcbiAgY29uc3Qge1xuICAgIHBsYWNlbWVudCxcbiAgICBwbGF0Zm9ybSxcbiAgICBlbGVtZW50c1xuICB9ID0gc3RhdGU7XG4gIGNvbnN0IHJ0bCA9IGF3YWl0IChwbGF0Zm9ybS5pc1JUTCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNSVEwoZWxlbWVudHMuZmxvYXRpbmcpKTtcbiAgY29uc3Qgc2lkZSA9IGdldFNpZGUocGxhY2VtZW50KTtcbiAgY29uc3QgYWxpZ25tZW50ID0gZ2V0QWxpZ25tZW50KHBsYWNlbWVudCk7XG4gIGNvbnN0IGlzVmVydGljYWwgPSBnZXRTaWRlQXhpcyhwbGFjZW1lbnQpID09PSAneSc7XG4gIGNvbnN0IG1haW5BeGlzTXVsdGkgPSBvcmlnaW5TaWRlcy5oYXMoc2lkZSkgPyAtMSA6IDE7XG4gIGNvbnN0IGNyb3NzQXhpc011bHRpID0gcnRsICYmIGlzVmVydGljYWwgPyAtMSA6IDE7XG4gIGNvbnN0IHJhd1ZhbHVlID0gZXZhbHVhdGUob3B0aW9ucywgc3RhdGUpO1xuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBwcmVmZXItY29uc3RcbiAgbGV0IHtcbiAgICBtYWluQXhpcyxcbiAgICBjcm9zc0F4aXMsXG4gICAgYWxpZ25tZW50QXhpc1xuICB9ID0gdHlwZW9mIHJhd1ZhbHVlID09PSAnbnVtYmVyJyA/IHtcbiAgICBtYWluQXhpczogcmF3VmFsdWUsXG4gICAgY3Jvc3NBeGlzOiAwLFxuICAgIGFsaWdubWVudEF4aXM6IG51bGxcbiAgfSA6IHtcbiAgICBtYWluQXhpczogcmF3VmFsdWUubWFpbkF4aXMgfHwgMCxcbiAgICBjcm9zc0F4aXM6IHJhd1ZhbHVlLmNyb3NzQXhpcyB8fCAwLFxuICAgIGFsaWdubWVudEF4aXM6IHJhd1ZhbHVlLmFsaWdubWVudEF4aXNcbiAgfTtcbiAgaWYgKGFsaWdubWVudCAmJiB0eXBlb2YgYWxpZ25tZW50QXhpcyA9PT0gJ251bWJlcicpIHtcbiAgICBjcm9zc0F4aXMgPSBhbGlnbm1lbnQgPT09ICdlbmQnID8gYWxpZ25tZW50QXhpcyAqIC0xIDogYWxpZ25tZW50QXhpcztcbiAgfVxuICByZXR1cm4gaXNWZXJ0aWNhbCA/IHtcbiAgICB4OiBjcm9zc0F4aXMgKiBjcm9zc0F4aXNNdWx0aSxcbiAgICB5OiBtYWluQXhpcyAqIG1haW5BeGlzTXVsdGlcbiAgfSA6IHtcbiAgICB4OiBtYWluQXhpcyAqIG1haW5BeGlzTXVsdGksXG4gICAgeTogY3Jvc3NBeGlzICogY3Jvc3NBeGlzTXVsdGlcbiAgfTtcbn1cblxuLyoqXG4gKiBNb2RpZmllcyB0aGUgcGxhY2VtZW50IGJ5IHRyYW5zbGF0aW5nIHRoZSBmbG9hdGluZyBlbGVtZW50IGFsb25nIHRoZVxuICogc3BlY2lmaWVkIGF4ZXMuXG4gKiBBIG51bWJlciAoc2hvcnRoYW5kIGZvciBgbWFpbkF4aXNgIG9yIGRpc3RhbmNlKSwgb3IgYW4gYXhlcyBjb25maWd1cmF0aW9uXG4gKiBvYmplY3QgbWF5IGJlIHBhc3NlZC5cbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9vZmZzZXRcbiAqL1xuY29uc3Qgb2Zmc2V0ID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSAwO1xuICB9XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ29mZnNldCcsXG4gICAgb3B0aW9ucyxcbiAgICBhc3luYyBmbihzdGF0ZSkge1xuICAgICAgdmFyIF9taWRkbGV3YXJlRGF0YSRvZmZzZSwgX21pZGRsZXdhcmVEYXRhJGFycm93O1xuICAgICAgY29uc3Qge1xuICAgICAgICB4LFxuICAgICAgICB5LFxuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIG1pZGRsZXdhcmVEYXRhXG4gICAgICB9ID0gc3RhdGU7XG4gICAgICBjb25zdCBkaWZmQ29vcmRzID0gYXdhaXQgY29udmVydFZhbHVlVG9Db29yZHMoc3RhdGUsIG9wdGlvbnMpO1xuXG4gICAgICAvLyBJZiB0aGUgcGxhY2VtZW50IGlzIHRoZSBzYW1lIGFuZCB0aGUgYXJyb3cgY2F1c2VkIGFuIGFsaWdubWVudCBvZmZzZXRcbiAgICAgIC8vIHRoZW4gd2UgZG9uJ3QgbmVlZCB0byBjaGFuZ2UgdGhlIHBvc2l0aW9uaW5nIGNvb3JkaW5hdGVzLlxuICAgICAgaWYgKHBsYWNlbWVudCA9PT0gKChfbWlkZGxld2FyZURhdGEkb2Zmc2UgPSBtaWRkbGV3YXJlRGF0YS5vZmZzZXQpID09IG51bGwgPyB2b2lkIDAgOiBfbWlkZGxld2FyZURhdGEkb2Zmc2UucGxhY2VtZW50KSAmJiAoX21pZGRsZXdhcmVEYXRhJGFycm93ID0gbWlkZGxld2FyZURhdGEuYXJyb3cpICE9IG51bGwgJiYgX21pZGRsZXdhcmVEYXRhJGFycm93LmFsaWdubWVudE9mZnNldCkge1xuICAgICAgICByZXR1cm4ge307XG4gICAgICB9XG4gICAgICByZXR1cm4ge1xuICAgICAgICB4OiB4ICsgZGlmZkNvb3Jkcy54LFxuICAgICAgICB5OiB5ICsgZGlmZkNvb3Jkcy55LFxuICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgLi4uZGlmZkNvb3JkcyxcbiAgICAgICAgICBwbGFjZW1lbnRcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICB9XG4gIH07XG59O1xuXG4vKipcbiAqIE9wdGltaXplcyB0aGUgdmlzaWJpbGl0eSBvZiB0aGUgZmxvYXRpbmcgZWxlbWVudCBieSBzaGlmdGluZyBpdCBpbiBvcmRlciB0b1xuICoga2VlcCBpdCBpbiB2aWV3IHdoZW4gaXQgd2lsbCBvdmVyZmxvdyB0aGUgY2xpcHBpbmcgYm91bmRhcnkuXG4gKiBAc2VlIGh0dHBzOi8vZmxvYXRpbmctdWkuY29tL2RvY3Mvc2hpZnRcbiAqL1xuY29uc3Qgc2hpZnQgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICBpZiAob3B0aW9ucyA9PT0gdm9pZCAwKSB7XG4gICAgb3B0aW9ucyA9IHt9O1xuICB9XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ3NoaWZ0JyxcbiAgICBvcHRpb25zLFxuICAgIGFzeW5jIGZuKHN0YXRlKSB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIHgsXG4gICAgICAgIHksXG4gICAgICAgIHBsYWNlbWVudFxuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBtYWluQXhpczogY2hlY2tNYWluQXhpcyA9IHRydWUsXG4gICAgICAgIGNyb3NzQXhpczogY2hlY2tDcm9zc0F4aXMgPSBmYWxzZSxcbiAgICAgICAgbGltaXRlciA9IHtcbiAgICAgICAgICBmbjogX3JlZiA9PiB7XG4gICAgICAgICAgICBsZXQge1xuICAgICAgICAgICAgICB4LFxuICAgICAgICAgICAgICB5XG4gICAgICAgICAgICB9ID0gX3JlZjtcbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgIHgsXG4gICAgICAgICAgICAgIHlcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnNcbiAgICAgIH0gPSBldmFsdWF0ZShvcHRpb25zLCBzdGF0ZSk7XG4gICAgICBjb25zdCBjb29yZHMgPSB7XG4gICAgICAgIHgsXG4gICAgICAgIHlcbiAgICAgIH07XG4gICAgICBjb25zdCBvdmVyZmxvdyA9IGF3YWl0IGRldGVjdE92ZXJmbG93KHN0YXRlLCBkZXRlY3RPdmVyZmxvd09wdGlvbnMpO1xuICAgICAgY29uc3QgY3Jvc3NBeGlzID0gZ2V0U2lkZUF4aXMoZ2V0U2lkZShwbGFjZW1lbnQpKTtcbiAgICAgIGNvbnN0IG1haW5BeGlzID0gZ2V0T3Bwb3NpdGVBeGlzKGNyb3NzQXhpcyk7XG4gICAgICBsZXQgbWFpbkF4aXNDb29yZCA9IGNvb3Jkc1ttYWluQXhpc107XG4gICAgICBsZXQgY3Jvc3NBeGlzQ29vcmQgPSBjb29yZHNbY3Jvc3NBeGlzXTtcbiAgICAgIGlmIChjaGVja01haW5BeGlzKSB7XG4gICAgICAgIGNvbnN0IG1pblNpZGUgPSBtYWluQXhpcyA9PT0gJ3knID8gJ3RvcCcgOiAnbGVmdCc7XG4gICAgICAgIGNvbnN0IG1heFNpZGUgPSBtYWluQXhpcyA9PT0gJ3knID8gJ2JvdHRvbScgOiAncmlnaHQnO1xuICAgICAgICBjb25zdCBtaW4gPSBtYWluQXhpc0Nvb3JkICsgb3ZlcmZsb3dbbWluU2lkZV07XG4gICAgICAgIGNvbnN0IG1heCA9IG1haW5BeGlzQ29vcmQgLSBvdmVyZmxvd1ttYXhTaWRlXTtcbiAgICAgICAgbWFpbkF4aXNDb29yZCA9IGNsYW1wKG1pbiwgbWFpbkF4aXNDb29yZCwgbWF4KTtcbiAgICAgIH1cbiAgICAgIGlmIChjaGVja0Nyb3NzQXhpcykge1xuICAgICAgICBjb25zdCBtaW5TaWRlID0gY3Jvc3NBeGlzID09PSAneScgPyAndG9wJyA6ICdsZWZ0JztcbiAgICAgICAgY29uc3QgbWF4U2lkZSA9IGNyb3NzQXhpcyA9PT0gJ3knID8gJ2JvdHRvbScgOiAncmlnaHQnO1xuICAgICAgICBjb25zdCBtaW4gPSBjcm9zc0F4aXNDb29yZCArIG92ZXJmbG93W21pblNpZGVdO1xuICAgICAgICBjb25zdCBtYXggPSBjcm9zc0F4aXNDb29yZCAtIG92ZXJmbG93W21heFNpZGVdO1xuICAgICAgICBjcm9zc0F4aXNDb29yZCA9IGNsYW1wKG1pbiwgY3Jvc3NBeGlzQ29vcmQsIG1heCk7XG4gICAgICB9XG4gICAgICBjb25zdCBsaW1pdGVkQ29vcmRzID0gbGltaXRlci5mbih7XG4gICAgICAgIC4uLnN0YXRlLFxuICAgICAgICBbbWFpbkF4aXNdOiBtYWluQXhpc0Nvb3JkLFxuICAgICAgICBbY3Jvc3NBeGlzXTogY3Jvc3NBeGlzQ29vcmRcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgLi4ubGltaXRlZENvb3JkcyxcbiAgICAgICAgZGF0YToge1xuICAgICAgICAgIHg6IGxpbWl0ZWRDb29yZHMueCAtIHgsXG4gICAgICAgICAgeTogbGltaXRlZENvb3Jkcy55IC0geSxcbiAgICAgICAgICBlbmFibGVkOiB7XG4gICAgICAgICAgICBbbWFpbkF4aXNdOiBjaGVja01haW5BeGlzLFxuICAgICAgICAgICAgW2Nyb3NzQXhpc106IGNoZWNrQ3Jvc3NBeGlzXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9O1xuICAgIH1cbiAgfTtcbn07XG4vKipcbiAqIEJ1aWx0LWluIGBsaW1pdGVyYCB0aGF0IHdpbGwgc3RvcCBgc2hpZnQoKWAgYXQgYSBjZXJ0YWluIHBvaW50LlxuICovXG5jb25zdCBsaW1pdFNoaWZ0ID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG9wdGlvbnMsXG4gICAgZm4oc3RhdGUpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgeCxcbiAgICAgICAgeSxcbiAgICAgICAgcGxhY2VtZW50LFxuICAgICAgICByZWN0cyxcbiAgICAgICAgbWlkZGxld2FyZURhdGFcbiAgICAgIH0gPSBzdGF0ZTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgb2Zmc2V0ID0gMCxcbiAgICAgICAgbWFpbkF4aXM6IGNoZWNrTWFpbkF4aXMgPSB0cnVlLFxuICAgICAgICBjcm9zc0F4aXM6IGNoZWNrQ3Jvc3NBeGlzID0gdHJ1ZVxuICAgICAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKTtcbiAgICAgIGNvbnN0IGNvb3JkcyA9IHtcbiAgICAgICAgeCxcbiAgICAgICAgeVxuICAgICAgfTtcbiAgICAgIGNvbnN0IGNyb3NzQXhpcyA9IGdldFNpZGVBeGlzKHBsYWNlbWVudCk7XG4gICAgICBjb25zdCBtYWluQXhpcyA9IGdldE9wcG9zaXRlQXhpcyhjcm9zc0F4aXMpO1xuICAgICAgbGV0IG1haW5BeGlzQ29vcmQgPSBjb29yZHNbbWFpbkF4aXNdO1xuICAgICAgbGV0IGNyb3NzQXhpc0Nvb3JkID0gY29vcmRzW2Nyb3NzQXhpc107XG4gICAgICBjb25zdCByYXdPZmZzZXQgPSBldmFsdWF0ZShvZmZzZXQsIHN0YXRlKTtcbiAgICAgIGNvbnN0IGNvbXB1dGVkT2Zmc2V0ID0gdHlwZW9mIHJhd09mZnNldCA9PT0gJ251bWJlcicgPyB7XG4gICAgICAgIG1haW5BeGlzOiByYXdPZmZzZXQsXG4gICAgICAgIGNyb3NzQXhpczogMFxuICAgICAgfSA6IHtcbiAgICAgICAgbWFpbkF4aXM6IDAsXG4gICAgICAgIGNyb3NzQXhpczogMCxcbiAgICAgICAgLi4ucmF3T2Zmc2V0XG4gICAgICB9O1xuICAgICAgaWYgKGNoZWNrTWFpbkF4aXMpIHtcbiAgICAgICAgY29uc3QgbGVuID0gbWFpbkF4aXMgPT09ICd5JyA/ICdoZWlnaHQnIDogJ3dpZHRoJztcbiAgICAgICAgY29uc3QgbGltaXRNaW4gPSByZWN0cy5yZWZlcmVuY2VbbWFpbkF4aXNdIC0gcmVjdHMuZmxvYXRpbmdbbGVuXSArIGNvbXB1dGVkT2Zmc2V0Lm1haW5BeGlzO1xuICAgICAgICBjb25zdCBsaW1pdE1heCA9IHJlY3RzLnJlZmVyZW5jZVttYWluQXhpc10gKyByZWN0cy5yZWZlcmVuY2VbbGVuXSAtIGNvbXB1dGVkT2Zmc2V0Lm1haW5BeGlzO1xuICAgICAgICBpZiAobWFpbkF4aXNDb29yZCA8IGxpbWl0TWluKSB7XG4gICAgICAgICAgbWFpbkF4aXNDb29yZCA9IGxpbWl0TWluO1xuICAgICAgICB9IGVsc2UgaWYgKG1haW5BeGlzQ29vcmQgPiBsaW1pdE1heCkge1xuICAgICAgICAgIG1haW5BeGlzQ29vcmQgPSBsaW1pdE1heDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKGNoZWNrQ3Jvc3NBeGlzKSB7XG4gICAgICAgIHZhciBfbWlkZGxld2FyZURhdGEkb2Zmc2UsIF9taWRkbGV3YXJlRGF0YSRvZmZzZTI7XG4gICAgICAgIGNvbnN0IGxlbiA9IG1haW5BeGlzID09PSAneScgPyAnd2lkdGgnIDogJ2hlaWdodCc7XG4gICAgICAgIGNvbnN0IGlzT3JpZ2luU2lkZSA9IG9yaWdpblNpZGVzLmhhcyhnZXRTaWRlKHBsYWNlbWVudCkpO1xuICAgICAgICBjb25zdCBsaW1pdE1pbiA9IHJlY3RzLnJlZmVyZW5jZVtjcm9zc0F4aXNdIC0gcmVjdHMuZmxvYXRpbmdbbGVuXSArIChpc09yaWdpblNpZGUgPyAoKF9taWRkbGV3YXJlRGF0YSRvZmZzZSA9IG1pZGRsZXdhcmVEYXRhLm9mZnNldCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9taWRkbGV3YXJlRGF0YSRvZmZzZVtjcm9zc0F4aXNdKSB8fCAwIDogMCkgKyAoaXNPcmlnaW5TaWRlID8gMCA6IGNvbXB1dGVkT2Zmc2V0LmNyb3NzQXhpcyk7XG4gICAgICAgIGNvbnN0IGxpbWl0TWF4ID0gcmVjdHMucmVmZXJlbmNlW2Nyb3NzQXhpc10gKyByZWN0cy5yZWZlcmVuY2VbbGVuXSArIChpc09yaWdpblNpZGUgPyAwIDogKChfbWlkZGxld2FyZURhdGEkb2Zmc2UyID0gbWlkZGxld2FyZURhdGEub2Zmc2V0KSA9PSBudWxsID8gdm9pZCAwIDogX21pZGRsZXdhcmVEYXRhJG9mZnNlMltjcm9zc0F4aXNdKSB8fCAwKSAtIChpc09yaWdpblNpZGUgPyBjb21wdXRlZE9mZnNldC5jcm9zc0F4aXMgOiAwKTtcbiAgICAgICAgaWYgKGNyb3NzQXhpc0Nvb3JkIDwgbGltaXRNaW4pIHtcbiAgICAgICAgICBjcm9zc0F4aXNDb29yZCA9IGxpbWl0TWluO1xuICAgICAgICB9IGVsc2UgaWYgKGNyb3NzQXhpc0Nvb3JkID4gbGltaXRNYXgpIHtcbiAgICAgICAgICBjcm9zc0F4aXNDb29yZCA9IGxpbWl0TWF4O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4ge1xuICAgICAgICBbbWFpbkF4aXNdOiBtYWluQXhpc0Nvb3JkLFxuICAgICAgICBbY3Jvc3NBeGlzXTogY3Jvc3NBeGlzQ29vcmRcbiAgICAgIH07XG4gICAgfVxuICB9O1xufTtcblxuLyoqXG4gKiBQcm92aWRlcyBkYXRhIHRoYXQgYWxsb3dzIHlvdSB0byBjaGFuZ2UgdGhlIHNpemUgb2YgdGhlIGZsb2F0aW5nIGVsZW1lbnQg4oCUXG4gKiBmb3IgaW5zdGFuY2UsIHByZXZlbnQgaXQgZnJvbSBvdmVyZmxvd2luZyB0aGUgY2xpcHBpbmcgYm91bmRhcnkgb3IgbWF0Y2ggdGhlXG4gKiB3aWR0aCBvZiB0aGUgcmVmZXJlbmNlIGVsZW1lbnQuXG4gKiBAc2VlIGh0dHBzOi8vZmxvYXRpbmctdWkuY29tL2RvY3Mvc2l6ZVxuICovXG5jb25zdCBzaXplID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG5hbWU6ICdzaXplJyxcbiAgICBvcHRpb25zLFxuICAgIGFzeW5jIGZuKHN0YXRlKSB7XG4gICAgICB2YXIgX3N0YXRlJG1pZGRsZXdhcmVEYXRhLCBfc3RhdGUkbWlkZGxld2FyZURhdGEyO1xuICAgICAgY29uc3Qge1xuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIHJlY3RzLFxuICAgICAgICBwbGF0Zm9ybSxcbiAgICAgICAgZWxlbWVudHNcbiAgICAgIH0gPSBzdGF0ZTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgYXBwbHkgPSAoKSA9PiB7fSxcbiAgICAgICAgLi4uZGV0ZWN0T3ZlcmZsb3dPcHRpb25zXG4gICAgICB9ID0gZXZhbHVhdGUob3B0aW9ucywgc3RhdGUpO1xuICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwgZGV0ZWN0T3ZlcmZsb3dPcHRpb25zKTtcbiAgICAgIGNvbnN0IHNpZGUgPSBnZXRTaWRlKHBsYWNlbWVudCk7XG4gICAgICBjb25zdCBhbGlnbm1lbnQgPSBnZXRBbGlnbm1lbnQocGxhY2VtZW50KTtcbiAgICAgIGNvbnN0IGlzWUF4aXMgPSBnZXRTaWRlQXhpcyhwbGFjZW1lbnQpID09PSAneSc7XG4gICAgICBjb25zdCB7XG4gICAgICAgIHdpZHRoLFxuICAgICAgICBoZWlnaHRcbiAgICAgIH0gPSByZWN0cy5mbG9hdGluZztcbiAgICAgIGxldCBoZWlnaHRTaWRlO1xuICAgICAgbGV0IHdpZHRoU2lkZTtcbiAgICAgIGlmIChzaWRlID09PSAndG9wJyB8fCBzaWRlID09PSAnYm90dG9tJykge1xuICAgICAgICBoZWlnaHRTaWRlID0gc2lkZTtcbiAgICAgICAgd2lkdGhTaWRlID0gYWxpZ25tZW50ID09PSAoKGF3YWl0IChwbGF0Zm9ybS5pc1JUTCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNSVEwoZWxlbWVudHMuZmxvYXRpbmcpKSkgPyAnc3RhcnQnIDogJ2VuZCcpID8gJ2xlZnQnIDogJ3JpZ2h0JztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHdpZHRoU2lkZSA9IHNpZGU7XG4gICAgICAgIGhlaWdodFNpZGUgPSBhbGlnbm1lbnQgPT09ICdlbmQnID8gJ3RvcCcgOiAnYm90dG9tJztcbiAgICAgIH1cbiAgICAgIGNvbnN0IG1heGltdW1DbGlwcGluZ0hlaWdodCA9IGhlaWdodCAtIG92ZXJmbG93LnRvcCAtIG92ZXJmbG93LmJvdHRvbTtcbiAgICAgIGNvbnN0IG1heGltdW1DbGlwcGluZ1dpZHRoID0gd2lkdGggLSBvdmVyZmxvdy5sZWZ0IC0gb3ZlcmZsb3cucmlnaHQ7XG4gICAgICBjb25zdCBvdmVyZmxvd0F2YWlsYWJsZUhlaWdodCA9IG1pbihoZWlnaHQgLSBvdmVyZmxvd1toZWlnaHRTaWRlXSwgbWF4aW11bUNsaXBwaW5nSGVpZ2h0KTtcbiAgICAgIGNvbnN0IG92ZXJmbG93QXZhaWxhYmxlV2lkdGggPSBtaW4od2lkdGggLSBvdmVyZmxvd1t3aWR0aFNpZGVdLCBtYXhpbXVtQ2xpcHBpbmdXaWR0aCk7XG4gICAgICBjb25zdCBub1NoaWZ0ID0gIXN0YXRlLm1pZGRsZXdhcmVEYXRhLnNoaWZ0O1xuICAgICAgbGV0IGF2YWlsYWJsZUhlaWdodCA9IG92ZXJmbG93QXZhaWxhYmxlSGVpZ2h0O1xuICAgICAgbGV0IGF2YWlsYWJsZVdpZHRoID0gb3ZlcmZsb3dBdmFpbGFibGVXaWR0aDtcbiAgICAgIGlmICgoX3N0YXRlJG1pZGRsZXdhcmVEYXRhID0gc3RhdGUubWlkZGxld2FyZURhdGEuc2hpZnQpICE9IG51bGwgJiYgX3N0YXRlJG1pZGRsZXdhcmVEYXRhLmVuYWJsZWQueCkge1xuICAgICAgICBhdmFpbGFibGVXaWR0aCA9IG1heGltdW1DbGlwcGluZ1dpZHRoO1xuICAgICAgfVxuICAgICAgaWYgKChfc3RhdGUkbWlkZGxld2FyZURhdGEyID0gc3RhdGUubWlkZGxld2FyZURhdGEuc2hpZnQpICE9IG51bGwgJiYgX3N0YXRlJG1pZGRsZXdhcmVEYXRhMi5lbmFibGVkLnkpIHtcbiAgICAgICAgYXZhaWxhYmxlSGVpZ2h0ID0gbWF4aW11bUNsaXBwaW5nSGVpZ2h0O1xuICAgICAgfVxuICAgICAgaWYgKG5vU2hpZnQgJiYgIWFsaWdubWVudCkge1xuICAgICAgICBjb25zdCB4TWluID0gbWF4KG92ZXJmbG93LmxlZnQsIDApO1xuICAgICAgICBjb25zdCB4TWF4ID0gbWF4KG92ZXJmbG93LnJpZ2h0LCAwKTtcbiAgICAgICAgY29uc3QgeU1pbiA9IG1heChvdmVyZmxvdy50b3AsIDApO1xuICAgICAgICBjb25zdCB5TWF4ID0gbWF4KG92ZXJmbG93LmJvdHRvbSwgMCk7XG4gICAgICAgIGlmIChpc1lBeGlzKSB7XG4gICAgICAgICAgYXZhaWxhYmxlV2lkdGggPSB3aWR0aCAtIDIgKiAoeE1pbiAhPT0gMCB8fCB4TWF4ICE9PSAwID8geE1pbiArIHhNYXggOiBtYXgob3ZlcmZsb3cubGVmdCwgb3ZlcmZsb3cucmlnaHQpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBhdmFpbGFibGVIZWlnaHQgPSBoZWlnaHQgLSAyICogKHlNaW4gIT09IDAgfHwgeU1heCAhPT0gMCA/IHlNaW4gKyB5TWF4IDogbWF4KG92ZXJmbG93LnRvcCwgb3ZlcmZsb3cuYm90dG9tKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGF3YWl0IGFwcGx5KHtcbiAgICAgICAgLi4uc3RhdGUsXG4gICAgICAgIGF2YWlsYWJsZVdpZHRoLFxuICAgICAgICBhdmFpbGFibGVIZWlnaHRcbiAgICAgIH0pO1xuICAgICAgY29uc3QgbmV4dERpbWVuc2lvbnMgPSBhd2FpdCBwbGF0Zm9ybS5nZXREaW1lbnNpb25zKGVsZW1lbnRzLmZsb2F0aW5nKTtcbiAgICAgIGlmICh3aWR0aCAhPT0gbmV4dERpbWVuc2lvbnMud2lkdGggfHwgaGVpZ2h0ICE9PSBuZXh0RGltZW5zaW9ucy5oZWlnaHQpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICByZXNldDoge1xuICAgICAgICAgICAgcmVjdHM6IHRydWVcbiAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICB9XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuICB9O1xufTtcblxuZXhwb3J0IHsgYXJyb3csIGF1dG9QbGFjZW1lbnQsIGNvbXB1dGVQb3NpdGlvbiwgZGV0ZWN0T3ZlcmZsb3csIGZsaXAsIGhpZGUsIGlubGluZSwgbGltaXRTaGlmdCwgb2Zmc2V0LCBzaGlmdCwgc2l6ZSB9O1xuIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./node_modules/@floating-ui/core/dist/floating-ui.core.mjs\n\n}"); +eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ arrow: () => (/* binding */ arrow),\n/* harmony export */ autoPlacement: () => (/* binding */ autoPlacement),\n/* harmony export */ computePosition: () => (/* binding */ computePosition),\n/* harmony export */ detectOverflow: () => (/* binding */ detectOverflow),\n/* harmony export */ flip: () => (/* binding */ flip),\n/* harmony export */ hide: () => (/* binding */ hide),\n/* harmony export */ inline: () => (/* binding */ inline),\n/* harmony export */ limitShift: () => (/* binding */ limitShift),\n/* harmony export */ offset: () => (/* binding */ offset),\n/* harmony export */ rectToClientRect: () => (/* reexport safe */ _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect),\n/* harmony export */ shift: () => (/* binding */ shift),\n/* harmony export */ size: () => (/* binding */ size)\n/* harmony export */ });\n/* harmony import */ var _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @floating-ui/utils */ \"./node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs\");\n\n\n\nfunction computeCoordsFromPlacement(_ref, placement, rtl) {\n let {\n reference,\n floating\n } = _ref;\n const sideAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement);\n const alignmentAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentAxis)(placement);\n const alignLength = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAxisLength)(alignmentAxis);\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const isVertical = sideAxis === 'y';\n const commonX = reference.x + reference.width / 2 - floating.width / 2;\n const commonY = reference.y + reference.height / 2 - floating.height / 2;\n const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;\n let coords;\n switch (side) {\n case 'top':\n coords = {\n x: commonX,\n y: reference.y - floating.height\n };\n break;\n case 'bottom':\n coords = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n case 'right':\n coords = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n case 'left':\n coords = {\n x: reference.x - floating.width,\n y: commonY\n };\n break;\n default:\n coords = {\n x: reference.x,\n y: reference.y\n };\n }\n switch ((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement)) {\n case 'start':\n coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);\n break;\n case 'end':\n coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1);\n break;\n }\n return coords;\n}\n\n/**\n * Computes the `x` and `y` coordinates that will place the floating element\n * next to a given reference element.\n *\n * This export does not have any `platform` interface logic. You will need to\n * write one for the platform you are using Floating UI with.\n */\nconst computePosition = async (reference, floating, config) => {\n const {\n placement = 'bottom',\n strategy = 'absolute',\n middleware = [],\n platform\n } = config;\n const validMiddleware = middleware.filter(Boolean);\n const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating));\n let rects = await platform.getElementRects({\n reference,\n floating,\n strategy\n });\n let {\n x,\n y\n } = computeCoordsFromPlacement(rects, placement, rtl);\n let statefulPlacement = placement;\n let middlewareData = {};\n let resetCount = 0;\n for (let i = 0; i < validMiddleware.length; i++) {\n const {\n name,\n fn\n } = validMiddleware[i];\n const {\n x: nextX,\n y: nextY,\n data,\n reset\n } = await fn({\n x,\n y,\n initialPlacement: placement,\n placement: statefulPlacement,\n strategy,\n middlewareData,\n rects,\n platform,\n elements: {\n reference,\n floating\n }\n });\n x = nextX != null ? nextX : x;\n y = nextY != null ? nextY : y;\n middlewareData = {\n ...middlewareData,\n [name]: {\n ...middlewareData[name],\n ...data\n }\n };\n if (reset && resetCount <= 50) {\n resetCount++;\n if (typeof reset === 'object') {\n if (reset.placement) {\n statefulPlacement = reset.placement;\n }\n if (reset.rects) {\n rects = reset.rects === true ? await platform.getElementRects({\n reference,\n floating,\n strategy\n }) : reset.rects;\n }\n ({\n x,\n y\n } = computeCoordsFromPlacement(rects, statefulPlacement, rtl));\n }\n i = -1;\n }\n }\n return {\n x,\n y,\n placement: statefulPlacement,\n strategy,\n middlewareData\n };\n};\n\n/**\n * Resolves with an object of overflow side offsets that determine how much the\n * element is overflowing a given clipping boundary on each side.\n * - positive = overflowing the boundary by that number of pixels\n * - negative = how many pixels left before it will overflow\n * - 0 = lies flush with the boundary\n * @see https://floating-ui.com/docs/detectOverflow\n */\nasync function detectOverflow(state, options) {\n var _await$platform$isEle;\n if (options === void 0) {\n options = {};\n }\n const {\n x,\n y,\n platform,\n rects,\n elements,\n strategy\n } = state;\n const {\n boundary = 'clippingAncestors',\n rootBoundary = 'viewport',\n elementContext = 'floating',\n altBoundary = false,\n padding = 0\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const paddingObject = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getPaddingObject)(padding);\n const altContext = elementContext === 'floating' ? 'reference' : 'floating';\n const element = elements[altBoundary ? altContext : elementContext];\n const clippingClientRect = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(await platform.getClippingRect({\n element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))),\n boundary,\n rootBoundary,\n strategy\n }));\n const rect = elementContext === 'floating' ? {\n x,\n y,\n width: rects.floating.width,\n height: rects.floating.height\n } : rects.reference;\n const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating));\n const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || {\n x: 1,\n y: 1\n } : {\n x: 1,\n y: 1\n };\n const elementClientRect = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({\n elements,\n rect,\n offsetParent,\n strategy\n }) : rect);\n return {\n top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,\n bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,\n left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,\n right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x\n };\n}\n\n/**\n * Provides data to position an inner element of the floating element so that it\n * appears centered to the reference element.\n * @see https://floating-ui.com/docs/arrow\n */\nconst arrow = options => ({\n name: 'arrow',\n options,\n async fn(state) {\n const {\n x,\n y,\n placement,\n rects,\n platform,\n elements,\n middlewareData\n } = state;\n // Since `element` is required, we don't Partial<> the type.\n const {\n element,\n padding = 0\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state) || {};\n if (element == null) {\n return {};\n }\n const paddingObject = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getPaddingObject)(padding);\n const coords = {\n x,\n y\n };\n const axis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentAxis)(placement);\n const length = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAxisLength)(axis);\n const arrowDimensions = await platform.getDimensions(element);\n const isYAxis = axis === 'y';\n const minProp = isYAxis ? 'top' : 'left';\n const maxProp = isYAxis ? 'bottom' : 'right';\n const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';\n const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];\n const startDiff = coords[axis] - rects.reference[axis];\n const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element));\n let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0;\n\n // DOM platform can return `window` as the `offsetParent`.\n if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) {\n clientSize = elements.floating[clientProp] || rects.floating[length];\n }\n const centerToReference = endDiff / 2 - startDiff / 2;\n\n // If the padding is large enough that it causes the arrow to no longer be\n // centered, modify the padding so that it is centered.\n const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;\n const minPadding = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(paddingObject[minProp], largestPossiblePadding);\n const maxPadding = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(paddingObject[maxProp], largestPossiblePadding);\n\n // Make sure the arrow doesn't overflow the floating element if the center\n // point is outside the floating element's bounds.\n const min$1 = minPadding;\n const max = clientSize - arrowDimensions[length] - maxPadding;\n const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;\n const offset = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.clamp)(min$1, center, max);\n\n // If the reference is small enough that the arrow's padding causes it to\n // to point to nothing for an aligned placement, adjust the offset of the\n // floating element itself. To ensure `shift()` continues to take action,\n // a single reset is performed when this is true.\n const shouldAddOffset = !middlewareData.arrow && (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;\n const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0;\n return {\n [axis]: coords[axis] + alignmentOffset,\n data: {\n [axis]: offset,\n centerOffset: center - offset - alignmentOffset,\n ...(shouldAddOffset && {\n alignmentOffset\n })\n },\n reset: shouldAddOffset\n };\n }\n});\n\nfunction getPlacementList(alignment, autoAlignment, allowedPlacements) {\n const allowedPlacementsSortedByAlignment = alignment ? [...allowedPlacements.filter(placement => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) === alignment), ...allowedPlacements.filter(placement => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) !== alignment)] : allowedPlacements.filter(placement => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement) === placement);\n return allowedPlacementsSortedByAlignment.filter(placement => {\n if (alignment) {\n return (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement) === alignment || (autoAlignment ? (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAlignmentPlacement)(placement) !== placement : false);\n }\n return true;\n });\n}\n/**\n * Optimizes the visibility of the floating element by choosing the placement\n * that has the most space available automatically, without needing to specify a\n * preferred placement. Alternative to `flip`.\n * @see https://floating-ui.com/docs/autoPlacement\n */\nconst autoPlacement = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'autoPlacement',\n options,\n async fn(state) {\n var _middlewareData$autoP, _middlewareData$autoP2, _placementsThatFitOnE;\n const {\n rects,\n middlewareData,\n placement,\n platform,\n elements\n } = state;\n const {\n crossAxis = false,\n alignment,\n allowedPlacements = _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.placements,\n autoAlignment = true,\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const placements$1 = alignment !== undefined || allowedPlacements === _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.placements ? getPlacementList(alignment || null, autoAlignment, allowedPlacements) : allowedPlacements;\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const currentIndex = ((_middlewareData$autoP = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP.index) || 0;\n const currentPlacement = placements$1[currentIndex];\n if (currentPlacement == null) {\n return {};\n }\n const alignmentSides = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentSides)(currentPlacement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)));\n\n // Make `computeCoords` start from the right place.\n if (placement !== currentPlacement) {\n return {\n reset: {\n placement: placements$1[0]\n }\n };\n }\n const currentOverflows = [overflow[(0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(currentPlacement)], overflow[alignmentSides[0]], overflow[alignmentSides[1]]];\n const allOverflows = [...(((_middlewareData$autoP2 = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP2.overflows) || []), {\n placement: currentPlacement,\n overflows: currentOverflows\n }];\n const nextPlacement = placements$1[currentIndex + 1];\n\n // There are more placements to check.\n if (nextPlacement) {\n return {\n data: {\n index: currentIndex + 1,\n overflows: allOverflows\n },\n reset: {\n placement: nextPlacement\n }\n };\n }\n const placementsSortedByMostSpace = allOverflows.map(d => {\n const alignment = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(d.placement);\n return [d.placement, alignment && crossAxis ?\n // Check along the mainAxis and main crossAxis side.\n d.overflows.slice(0, 2).reduce((acc, v) => acc + v, 0) :\n // Check only the mainAxis.\n d.overflows[0], d.overflows];\n }).sort((a, b) => a[1] - b[1]);\n const placementsThatFitOnEachSide = placementsSortedByMostSpace.filter(d => d[2].slice(0,\n // Aligned placements should not check their opposite crossAxis\n // side.\n (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(d[0]) ? 2 : 3).every(v => v <= 0));\n const resetPlacement = ((_placementsThatFitOnE = placementsThatFitOnEachSide[0]) == null ? void 0 : _placementsThatFitOnE[0]) || placementsSortedByMostSpace[0][0];\n if (resetPlacement !== placement) {\n return {\n data: {\n index: currentIndex + 1,\n overflows: allOverflows\n },\n reset: {\n placement: resetPlacement\n }\n };\n }\n return {};\n }\n };\n};\n\n/**\n * Optimizes the visibility of the floating element by flipping the `placement`\n * in order to keep it in view when the preferred placement(s) will overflow the\n * clipping boundary. Alternative to `autoPlacement`.\n * @see https://floating-ui.com/docs/flip\n */\nconst flip = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'flip',\n options,\n async fn(state) {\n var _middlewareData$arrow, _middlewareData$flip;\n const {\n placement,\n middlewareData,\n rects,\n initialPlacement,\n platform,\n elements\n } = state;\n const {\n mainAxis: checkMainAxis = true,\n crossAxis: checkCrossAxis = true,\n fallbackPlacements: specifiedFallbackPlacements,\n fallbackStrategy = 'bestFit',\n fallbackAxisSideDirection = 'none',\n flipAlignment = true,\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n\n // If a reset by the arrow was caused due to an alignment offset being\n // added, we should skip any logic now since `flip()` has already done its\n // work.\n // https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643\n if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {\n return {};\n }\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const initialSideAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(initialPlacement);\n const isBasePlacement = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(initialPlacement) === initialPlacement;\n const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));\n const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [(0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositePlacement)(initialPlacement)] : (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getExpandedPlacements)(initialPlacement));\n const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== 'none';\n if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) {\n fallbackPlacements.push(...(0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAxisPlacements)(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));\n }\n const placements = [initialPlacement, ...fallbackPlacements];\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const overflows = [];\n let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];\n if (checkMainAxis) {\n overflows.push(overflow[side]);\n }\n if (checkCrossAxis) {\n const sides = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignmentSides)(placement, rects, rtl);\n overflows.push(overflow[sides[0]], overflow[sides[1]]);\n }\n overflowsData = [...overflowsData, {\n placement,\n overflows\n }];\n\n // One or more sides is overflowing.\n if (!overflows.every(side => side <= 0)) {\n var _middlewareData$flip2, _overflowsData$filter;\n const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;\n const nextPlacement = placements[nextIndex];\n if (nextPlacement) {\n const ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(nextPlacement) : false;\n if (!ignoreCrossAxisOverflow ||\n // We leave the current main axis only if every placement on that axis\n // overflows the main axis.\n overflowsData.every(d => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) {\n // Try next placement and re-run the lifecycle.\n return {\n data: {\n index: nextIndex,\n overflows: overflowsData\n },\n reset: {\n placement: nextPlacement\n }\n };\n }\n }\n\n // First, find the candidates that fit on the mainAxis side of overflow,\n // then find the placement that fits the best on the main crossAxis side.\n let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement;\n\n // Otherwise fallback.\n if (!resetPlacement) {\n switch (fallbackStrategy) {\n case 'bestFit':\n {\n var _overflowsData$filter2;\n const placement = (_overflowsData$filter2 = overflowsData.filter(d => {\n if (hasFallbackAxisSideDirection) {\n const currentSideAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(d.placement);\n return currentSideAxis === initialSideAxis ||\n // Create a bias to the `y` side axis due to horizontal\n // reading directions favoring greater width.\n currentSideAxis === 'y';\n }\n return true;\n }).map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0];\n if (placement) {\n resetPlacement = placement;\n }\n break;\n }\n case 'initialPlacement':\n resetPlacement = initialPlacement;\n break;\n }\n }\n if (placement !== resetPlacement) {\n return {\n reset: {\n placement: resetPlacement\n }\n };\n }\n }\n return {};\n }\n };\n};\n\nfunction getSideOffsets(overflow, rect) {\n return {\n top: overflow.top - rect.height,\n right: overflow.right - rect.width,\n bottom: overflow.bottom - rect.height,\n left: overflow.left - rect.width\n };\n}\nfunction isAnySideFullyClipped(overflow) {\n return _floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.sides.some(side => overflow[side] >= 0);\n}\n/**\n * Provides data to hide the floating element in applicable situations, such as\n * when it is not in the same clipping context as the reference element.\n * @see https://floating-ui.com/docs/hide\n */\nconst hide = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'hide',\n options,\n async fn(state) {\n const {\n rects\n } = state;\n const {\n strategy = 'referenceHidden',\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n switch (strategy) {\n case 'referenceHidden':\n {\n const overflow = await detectOverflow(state, {\n ...detectOverflowOptions,\n elementContext: 'reference'\n });\n const offsets = getSideOffsets(overflow, rects.reference);\n return {\n data: {\n referenceHiddenOffsets: offsets,\n referenceHidden: isAnySideFullyClipped(offsets)\n }\n };\n }\n case 'escaped':\n {\n const overflow = await detectOverflow(state, {\n ...detectOverflowOptions,\n altBoundary: true\n });\n const offsets = getSideOffsets(overflow, rects.floating);\n return {\n data: {\n escapedOffsets: offsets,\n escaped: isAnySideFullyClipped(offsets)\n }\n };\n }\n default:\n {\n return {};\n }\n }\n }\n };\n};\n\nfunction getBoundingRect(rects) {\n const minX = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(...rects.map(rect => rect.left));\n const minY = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(...rects.map(rect => rect.top));\n const maxX = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(...rects.map(rect => rect.right));\n const maxY = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(...rects.map(rect => rect.bottom));\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY\n };\n}\nfunction getRectsByLine(rects) {\n const sortedRects = rects.slice().sort((a, b) => a.y - b.y);\n const groups = [];\n let prevRect = null;\n for (let i = 0; i < sortedRects.length; i++) {\n const rect = sortedRects[i];\n if (!prevRect || rect.y - prevRect.y > prevRect.height / 2) {\n groups.push([rect]);\n } else {\n groups[groups.length - 1].push(rect);\n }\n prevRect = rect;\n }\n return groups.map(rect => (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(getBoundingRect(rect)));\n}\n/**\n * Provides improved positioning for inline reference elements that can span\n * over multiple lines, such as hyperlinks or range selections.\n * @see https://floating-ui.com/docs/inline\n */\nconst inline = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'inline',\n options,\n async fn(state) {\n const {\n placement,\n elements,\n rects,\n platform,\n strategy\n } = state;\n // A MouseEvent's client{X,Y} coords can be up to 2 pixels off a\n // ClientRect's bounds, despite the event listener being triggered. A\n // padding of 2 seems to handle this issue.\n const {\n padding = 2,\n x,\n y\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const nativeClientRects = Array.from((await (platform.getClientRects == null ? void 0 : platform.getClientRects(elements.reference))) || []);\n const clientRects = getRectsByLine(nativeClientRects);\n const fallback = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(getBoundingRect(nativeClientRects));\n const paddingObject = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getPaddingObject)(padding);\n function getBoundingClientRect() {\n // There are two rects and they are disjoined.\n if (clientRects.length === 2 && clientRects[0].left > clientRects[1].right && x != null && y != null) {\n // Find the first rect in which the point is fully inside.\n return clientRects.find(rect => x > rect.left - paddingObject.left && x < rect.right + paddingObject.right && y > rect.top - paddingObject.top && y < rect.bottom + paddingObject.bottom) || fallback;\n }\n\n // There are 2 or more connected rects.\n if (clientRects.length >= 2) {\n if ((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement) === 'y') {\n const firstRect = clientRects[0];\n const lastRect = clientRects[clientRects.length - 1];\n const isTop = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement) === 'top';\n const top = firstRect.top;\n const bottom = lastRect.bottom;\n const left = isTop ? firstRect.left : lastRect.left;\n const right = isTop ? firstRect.right : lastRect.right;\n const width = right - left;\n const height = bottom - top;\n return {\n top,\n bottom,\n left,\n right,\n width,\n height,\n x: left,\n y: top\n };\n }\n const isLeftSide = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement) === 'left';\n const maxRight = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(...clientRects.map(rect => rect.right));\n const minLeft = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(...clientRects.map(rect => rect.left));\n const measureRects = clientRects.filter(rect => isLeftSide ? rect.left === minLeft : rect.right === maxRight);\n const top = measureRects[0].top;\n const bottom = measureRects[measureRects.length - 1].bottom;\n const left = minLeft;\n const right = maxRight;\n const width = right - left;\n const height = bottom - top;\n return {\n top,\n bottom,\n left,\n right,\n width,\n height,\n x: left,\n y: top\n };\n }\n return fallback;\n }\n const resetRects = await platform.getElementRects({\n reference: {\n getBoundingClientRect\n },\n floating: elements.floating,\n strategy\n });\n if (rects.reference.x !== resetRects.reference.x || rects.reference.y !== resetRects.reference.y || rects.reference.width !== resetRects.reference.width || rects.reference.height !== resetRects.reference.height) {\n return {\n reset: {\n rects: resetRects\n }\n };\n }\n return {};\n }\n };\n};\n\nconst originSides = /*#__PURE__*/new Set(['left', 'top']);\n\n// For type backwards-compatibility, the `OffsetOptions` type was also\n// Derivable.\n\nasync function convertValueToCoords(state, options) {\n const {\n placement,\n platform,\n elements\n } = state;\n const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const alignment = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement);\n const isVertical = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement) === 'y';\n const mainAxisMulti = originSides.has(side) ? -1 : 1;\n const crossAxisMulti = rtl && isVertical ? -1 : 1;\n const rawValue = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n\n // eslint-disable-next-line prefer-const\n let {\n mainAxis,\n crossAxis,\n alignmentAxis\n } = typeof rawValue === 'number' ? {\n mainAxis: rawValue,\n crossAxis: 0,\n alignmentAxis: null\n } : {\n mainAxis: rawValue.mainAxis || 0,\n crossAxis: rawValue.crossAxis || 0,\n alignmentAxis: rawValue.alignmentAxis\n };\n if (alignment && typeof alignmentAxis === 'number') {\n crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis;\n }\n return isVertical ? {\n x: crossAxis * crossAxisMulti,\n y: mainAxis * mainAxisMulti\n } : {\n x: mainAxis * mainAxisMulti,\n y: crossAxis * crossAxisMulti\n };\n}\n\n/**\n * Modifies the placement by translating the floating element along the\n * specified axes.\n * A number (shorthand for `mainAxis` or distance), or an axes configuration\n * object may be passed.\n * @see https://floating-ui.com/docs/offset\n */\nconst offset = function (options) {\n if (options === void 0) {\n options = 0;\n }\n return {\n name: 'offset',\n options,\n async fn(state) {\n var _middlewareData$offse, _middlewareData$arrow;\n const {\n x,\n y,\n placement,\n middlewareData\n } = state;\n const diffCoords = await convertValueToCoords(state, options);\n\n // If the placement is the same and the arrow caused an alignment offset\n // then we don't need to change the positioning coordinates.\n if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {\n return {};\n }\n return {\n x: x + diffCoords.x,\n y: y + diffCoords.y,\n data: {\n ...diffCoords,\n placement\n }\n };\n }\n };\n};\n\n/**\n * Optimizes the visibility of the floating element by shifting it in order to\n * keep it in view when it will overflow the clipping boundary.\n * @see https://floating-ui.com/docs/shift\n */\nconst shift = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'shift',\n options,\n async fn(state) {\n const {\n x,\n y,\n placement\n } = state;\n const {\n mainAxis: checkMainAxis = true,\n crossAxis: checkCrossAxis = false,\n limiter = {\n fn: _ref => {\n let {\n x,\n y\n } = _ref;\n return {\n x,\n y\n };\n }\n },\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const coords = {\n x,\n y\n };\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const crossAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement));\n const mainAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAxis)(crossAxis);\n let mainAxisCoord = coords[mainAxis];\n let crossAxisCoord = coords[crossAxis];\n if (checkMainAxis) {\n const minSide = mainAxis === 'y' ? 'top' : 'left';\n const maxSide = mainAxis === 'y' ? 'bottom' : 'right';\n const min = mainAxisCoord + overflow[minSide];\n const max = mainAxisCoord - overflow[maxSide];\n mainAxisCoord = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.clamp)(min, mainAxisCoord, max);\n }\n if (checkCrossAxis) {\n const minSide = crossAxis === 'y' ? 'top' : 'left';\n const maxSide = crossAxis === 'y' ? 'bottom' : 'right';\n const min = crossAxisCoord + overflow[minSide];\n const max = crossAxisCoord - overflow[maxSide];\n crossAxisCoord = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.clamp)(min, crossAxisCoord, max);\n }\n const limitedCoords = limiter.fn({\n ...state,\n [mainAxis]: mainAxisCoord,\n [crossAxis]: crossAxisCoord\n });\n return {\n ...limitedCoords,\n data: {\n x: limitedCoords.x - x,\n y: limitedCoords.y - y,\n enabled: {\n [mainAxis]: checkMainAxis,\n [crossAxis]: checkCrossAxis\n }\n }\n };\n }\n };\n};\n/**\n * Built-in `limiter` that will stop `shift()` at a certain point.\n */\nconst limitShift = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n options,\n fn(state) {\n const {\n x,\n y,\n placement,\n rects,\n middlewareData\n } = state;\n const {\n offset = 0,\n mainAxis: checkMainAxis = true,\n crossAxis: checkCrossAxis = true\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const coords = {\n x,\n y\n };\n const crossAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement);\n const mainAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getOppositeAxis)(crossAxis);\n let mainAxisCoord = coords[mainAxis];\n let crossAxisCoord = coords[crossAxis];\n const rawOffset = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(offset, state);\n const computedOffset = typeof rawOffset === 'number' ? {\n mainAxis: rawOffset,\n crossAxis: 0\n } : {\n mainAxis: 0,\n crossAxis: 0,\n ...rawOffset\n };\n if (checkMainAxis) {\n const len = mainAxis === 'y' ? 'height' : 'width';\n const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis;\n const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis;\n if (mainAxisCoord < limitMin) {\n mainAxisCoord = limitMin;\n } else if (mainAxisCoord > limitMax) {\n mainAxisCoord = limitMax;\n }\n }\n if (checkCrossAxis) {\n var _middlewareData$offse, _middlewareData$offse2;\n const len = mainAxis === 'y' ? 'width' : 'height';\n const isOriginSide = originSides.has((0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement));\n const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis);\n const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0);\n if (crossAxisCoord < limitMin) {\n crossAxisCoord = limitMin;\n } else if (crossAxisCoord > limitMax) {\n crossAxisCoord = limitMax;\n }\n }\n return {\n [mainAxis]: mainAxisCoord,\n [crossAxis]: crossAxisCoord\n };\n }\n };\n};\n\n/**\n * Provides data that allows you to change the size of the floating element —\n * for instance, prevent it from overflowing the clipping boundary or match the\n * width of the reference element.\n * @see https://floating-ui.com/docs/size\n */\nconst size = function (options) {\n if (options === void 0) {\n options = {};\n }\n return {\n name: 'size',\n options,\n async fn(state) {\n var _state$middlewareData, _state$middlewareData2;\n const {\n placement,\n rects,\n platform,\n elements\n } = state;\n const {\n apply = () => {},\n ...detectOverflowOptions\n } = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.evaluate)(options, state);\n const overflow = await detectOverflow(state, detectOverflowOptions);\n const side = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSide)(placement);\n const alignment = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getAlignment)(placement);\n const isYAxis = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.getSideAxis)(placement) === 'y';\n const {\n width,\n height\n } = rects.floating;\n let heightSide;\n let widthSide;\n if (side === 'top' || side === 'bottom') {\n heightSide = side;\n widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right';\n } else {\n widthSide = side;\n heightSide = alignment === 'end' ? 'top' : 'bottom';\n }\n const maximumClippingHeight = height - overflow.top - overflow.bottom;\n const maximumClippingWidth = width - overflow.left - overflow.right;\n const overflowAvailableHeight = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(height - overflow[heightSide], maximumClippingHeight);\n const overflowAvailableWidth = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.min)(width - overflow[widthSide], maximumClippingWidth);\n const noShift = !state.middlewareData.shift;\n let availableHeight = overflowAvailableHeight;\n let availableWidth = overflowAvailableWidth;\n if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) {\n availableWidth = maximumClippingWidth;\n }\n if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) {\n availableHeight = maximumClippingHeight;\n }\n if (noShift && !alignment) {\n const xMin = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.left, 0);\n const xMax = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.right, 0);\n const yMin = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.top, 0);\n const yMax = (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.bottom, 0);\n if (isYAxis) {\n availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.left, overflow.right));\n } else {\n availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : (0,_floating_ui_utils__WEBPACK_IMPORTED_MODULE_0__.max)(overflow.top, overflow.bottom));\n }\n }\n await apply({\n ...state,\n availableWidth,\n availableHeight\n });\n const nextDimensions = await platform.getDimensions(elements.floating);\n if (width !== nextDimensions.width || height !== nextDimensions.height) {\n return {\n reset: {\n rects: true\n }\n };\n }\n return {};\n }\n };\n};\n\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGZsb2F0aW5nLXVpL2NvcmUvZGlzdC9mbG9hdGluZy11aS5jb3JlLm1qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQXNVO0FBQ2hSOztBQUV0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSixtQkFBbUIsK0RBQVc7QUFDOUIsd0JBQXdCLG9FQUFnQjtBQUN4QyxzQkFBc0IsaUVBQWE7QUFDbkMsZUFBZSwyREFBTztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVLGdFQUFZO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQiw0QkFBNEI7QUFDOUM7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSSxFQUFFLDREQUFRO0FBQ2Qsd0JBQXdCLG9FQUFnQjtBQUN4QztBQUNBO0FBQ0EsNkJBQTZCLG9FQUFnQjtBQUM3QztBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixvRUFBZ0I7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU0sRUFBRSw0REFBUTtBQUNoQjtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIsb0VBQWdCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsaUJBQWlCLG9FQUFnQjtBQUNqQyxtQkFBbUIsaUVBQWE7QUFDaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsdUJBQXVCLHVEQUFHO0FBQzFCLHVCQUF1Qix1REFBRzs7QUFFMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFtQix5REFBSzs7QUFFeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxREFBcUQsZ0VBQVk7QUFDakU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0EsQ0FBQzs7QUFFRDtBQUNBLG1HQUFtRyxnRUFBWSxxRUFBcUUsZ0VBQVksb0VBQW9FLDJEQUFPO0FBQzNRO0FBQ0E7QUFDQSxhQUFhLGdFQUFZLDhDQUE4QyxpRkFBNkI7QUFDcEc7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsMERBQVU7QUFDdEM7QUFDQTtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQiw0RUFBNEUsMERBQVU7QUFDdEY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkJBQTZCLHFFQUFpQjs7QUFFOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF5QywyREFBTztBQUNoRDtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUEwQixnRUFBWTtBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE1BQU0sZ0VBQVk7QUFDbEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUSxFQUFFLDREQUFROztBQUVsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFtQiwyREFBTztBQUMxQiw4QkFBOEIsK0RBQVc7QUFDekMsOEJBQThCLDJEQUFPO0FBQ3JDO0FBQ0Esc0dBQXNHLHdFQUFvQixzQkFBc0IseUVBQXFCO0FBQ3JLO0FBQ0E7QUFDQSxtQ0FBbUMsNkVBQXlCO0FBQzVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixxRUFBaUI7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87O0FBRVA7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0ZBQStGLCtEQUFXO0FBQzFHO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQywrREFBVztBQUM5QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZTtBQUNmO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRDQUE0QywrREFBVztBQUN2RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUyxxREFBSztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxlQUFlLHVEQUFHO0FBQ2xCLGVBQWUsdURBQUc7QUFDbEIsZUFBZSx1REFBRztBQUNsQixlQUFlLHVEQUFHO0FBQ2xCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0Isd0JBQXdCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixvRUFBZ0I7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUiwrQkFBK0IsS0FBSztBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRLEVBQUUsNERBQVE7QUFDbEI7QUFDQTtBQUNBLHVCQUF1QixvRUFBZ0I7QUFDdkMsNEJBQTRCLG9FQUFnQjtBQUM1QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGNBQWMsK0RBQVc7QUFDekI7QUFDQTtBQUNBLDBCQUEwQiwyREFBTztBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkJBQTZCLDJEQUFPO0FBQ3BDLDJCQUEyQix1REFBRztBQUM5QiwwQkFBMEIsdURBQUc7QUFDN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0EsZUFBZSwyREFBTztBQUN0QixvQkFBb0IsZ0VBQVk7QUFDaEMscUJBQXFCLCtEQUFXO0FBQ2hDO0FBQ0E7QUFDQSxtQkFBbUIsNERBQVE7O0FBRTNCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLCtEQUFXLENBQUMsMkRBQU87QUFDM0MsdUJBQXVCLG1FQUFlO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLHlEQUFLO0FBQzdCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlCQUF5Qix5REFBSztBQUM5QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVEsRUFBRSw0REFBUTtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QiwrREFBVztBQUNuQyx1QkFBdUIsbUVBQWU7QUFDdEM7QUFDQTtBQUNBLHdCQUF3Qiw0REFBUTtBQUNoQztBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLDJEQUFPO0FBQ3BEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSx3QkFBd0I7QUFDeEI7QUFDQSxRQUFRLEVBQUUsNERBQVE7QUFDbEI7QUFDQSxtQkFBbUIsMkRBQU87QUFDMUIsd0JBQXdCLGdFQUFZO0FBQ3BDLHNCQUFzQiwrREFBVztBQUNqQztBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLHVEQUFHO0FBQ3pDLHFDQUFxQyx1REFBRztBQUN4QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFCQUFxQix1REFBRztBQUN4QixxQkFBcUIsdURBQUc7QUFDeEIscUJBQXFCLHVEQUFHO0FBQ3hCLHFCQUFxQix1REFBRztBQUN4QjtBQUNBLGlGQUFpRix1REFBRztBQUNwRixVQUFVO0FBQ1YsbUZBQW1GLHVEQUFHO0FBQ3RGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRXNIIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vcGhhc2VyLWZyb250ZW5kLy4vbm9kZV9tb2R1bGVzL0BmbG9hdGluZy11aS9jb3JlL2Rpc3QvZmxvYXRpbmctdWkuY29yZS5tanM/ZTRjNyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBnZXRTaWRlQXhpcywgZ2V0QWxpZ25tZW50QXhpcywgZ2V0QXhpc0xlbmd0aCwgZ2V0U2lkZSwgZ2V0QWxpZ25tZW50LCBldmFsdWF0ZSwgZ2V0UGFkZGluZ09iamVjdCwgcmVjdFRvQ2xpZW50UmVjdCwgbWluLCBjbGFtcCwgcGxhY2VtZW50cywgZ2V0QWxpZ25tZW50U2lkZXMsIGdldE9wcG9zaXRlQWxpZ25tZW50UGxhY2VtZW50LCBnZXRPcHBvc2l0ZVBsYWNlbWVudCwgZ2V0RXhwYW5kZWRQbGFjZW1lbnRzLCBnZXRPcHBvc2l0ZUF4aXNQbGFjZW1lbnRzLCBzaWRlcywgbWF4LCBnZXRPcHBvc2l0ZUF4aXMgfSBmcm9tICdAZmxvYXRpbmctdWkvdXRpbHMnO1xuZXhwb3J0IHsgcmVjdFRvQ2xpZW50UmVjdCB9IGZyb20gJ0BmbG9hdGluZy11aS91dGlscyc7XG5cbmZ1bmN0aW9uIGNvbXB1dGVDb29yZHNGcm9tUGxhY2VtZW50KF9yZWYsIHBsYWNlbWVudCwgcnRsKSB7XG4gIGxldCB7XG4gICAgcmVmZXJlbmNlLFxuICAgIGZsb2F0aW5nXG4gIH0gPSBfcmVmO1xuICBjb25zdCBzaWRlQXhpcyA9IGdldFNpZGVBeGlzKHBsYWNlbWVudCk7XG4gIGNvbnN0IGFsaWdubWVudEF4aXMgPSBnZXRBbGlnbm1lbnRBeGlzKHBsYWNlbWVudCk7XG4gIGNvbnN0IGFsaWduTGVuZ3RoID0gZ2V0QXhpc0xlbmd0aChhbGlnbm1lbnRBeGlzKTtcbiAgY29uc3Qgc2lkZSA9IGdldFNpZGUocGxhY2VtZW50KTtcbiAgY29uc3QgaXNWZXJ0aWNhbCA9IHNpZGVBeGlzID09PSAneSc7XG4gIGNvbnN0IGNvbW1vblggPSByZWZlcmVuY2UueCArIHJlZmVyZW5jZS53aWR0aCAvIDIgLSBmbG9hdGluZy53aWR0aCAvIDI7XG4gIGNvbnN0IGNvbW1vblkgPSByZWZlcmVuY2UueSArIHJlZmVyZW5jZS5oZWlnaHQgLyAyIC0gZmxvYXRpbmcuaGVpZ2h0IC8gMjtcbiAgY29uc3QgY29tbW9uQWxpZ24gPSByZWZlcmVuY2VbYWxpZ25MZW5ndGhdIC8gMiAtIGZsb2F0aW5nW2FsaWduTGVuZ3RoXSAvIDI7XG4gIGxldCBjb29yZHM7XG4gIHN3aXRjaCAoc2lkZSkge1xuICAgIGNhc2UgJ3RvcCc6XG4gICAgICBjb29yZHMgPSB7XG4gICAgICAgIHg6IGNvbW1vblgsXG4gICAgICAgIHk6IHJlZmVyZW5jZS55IC0gZmxvYXRpbmcuaGVpZ2h0XG4gICAgICB9O1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnYm90dG9tJzpcbiAgICAgIGNvb3JkcyA9IHtcbiAgICAgICAgeDogY29tbW9uWCxcbiAgICAgICAgeTogcmVmZXJlbmNlLnkgKyByZWZlcmVuY2UuaGVpZ2h0XG4gICAgICB9O1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAncmlnaHQnOlxuICAgICAgY29vcmRzID0ge1xuICAgICAgICB4OiByZWZlcmVuY2UueCArIHJlZmVyZW5jZS53aWR0aCxcbiAgICAgICAgeTogY29tbW9uWVxuICAgICAgfTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ2xlZnQnOlxuICAgICAgY29vcmRzID0ge1xuICAgICAgICB4OiByZWZlcmVuY2UueCAtIGZsb2F0aW5nLndpZHRoLFxuICAgICAgICB5OiBjb21tb25ZXG4gICAgICB9O1xuICAgICAgYnJlYWs7XG4gICAgZGVmYXVsdDpcbiAgICAgIGNvb3JkcyA9IHtcbiAgICAgICAgeDogcmVmZXJlbmNlLngsXG4gICAgICAgIHk6IHJlZmVyZW5jZS55XG4gICAgICB9O1xuICB9XG4gIHN3aXRjaCAoZ2V0QWxpZ25tZW50KHBsYWNlbWVudCkpIHtcbiAgICBjYXNlICdzdGFydCc6XG4gICAgICBjb29yZHNbYWxpZ25tZW50QXhpc10gLT0gY29tbW9uQWxpZ24gKiAocnRsICYmIGlzVmVydGljYWwgPyAtMSA6IDEpO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnZW5kJzpcbiAgICAgIGNvb3Jkc1thbGlnbm1lbnRBeGlzXSArPSBjb21tb25BbGlnbiAqIChydGwgJiYgaXNWZXJ0aWNhbCA/IC0xIDogMSk7XG4gICAgICBicmVhaztcbiAgfVxuICByZXR1cm4gY29vcmRzO1xufVxuXG4vKipcbiAqIENvbXB1dGVzIHRoZSBgeGAgYW5kIGB5YCBjb29yZGluYXRlcyB0aGF0IHdpbGwgcGxhY2UgdGhlIGZsb2F0aW5nIGVsZW1lbnRcbiAqIG5leHQgdG8gYSBnaXZlbiByZWZlcmVuY2UgZWxlbWVudC5cbiAqXG4gKiBUaGlzIGV4cG9ydCBkb2VzIG5vdCBoYXZlIGFueSBgcGxhdGZvcm1gIGludGVyZmFjZSBsb2dpYy4gWW91IHdpbGwgbmVlZCB0b1xuICogd3JpdGUgb25lIGZvciB0aGUgcGxhdGZvcm0geW91IGFyZSB1c2luZyBGbG9hdGluZyBVSSB3aXRoLlxuICovXG5jb25zdCBjb21wdXRlUG9zaXRpb24gPSBhc3luYyAocmVmZXJlbmNlLCBmbG9hdGluZywgY29uZmlnKSA9PiB7XG4gIGNvbnN0IHtcbiAgICBwbGFjZW1lbnQgPSAnYm90dG9tJyxcbiAgICBzdHJhdGVneSA9ICdhYnNvbHV0ZScsXG4gICAgbWlkZGxld2FyZSA9IFtdLFxuICAgIHBsYXRmb3JtXG4gIH0gPSBjb25maWc7XG4gIGNvbnN0IHZhbGlkTWlkZGxld2FyZSA9IG1pZGRsZXdhcmUuZmlsdGVyKEJvb2xlYW4pO1xuICBjb25zdCBydGwgPSBhd2FpdCAocGxhdGZvcm0uaXNSVEwgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmlzUlRMKGZsb2F0aW5nKSk7XG4gIGxldCByZWN0cyA9IGF3YWl0IHBsYXRmb3JtLmdldEVsZW1lbnRSZWN0cyh7XG4gICAgcmVmZXJlbmNlLFxuICAgIGZsb2F0aW5nLFxuICAgIHN0cmF0ZWd5XG4gIH0pO1xuICBsZXQge1xuICAgIHgsXG4gICAgeVxuICB9ID0gY29tcHV0ZUNvb3Jkc0Zyb21QbGFjZW1lbnQocmVjdHMsIHBsYWNlbWVudCwgcnRsKTtcbiAgbGV0IHN0YXRlZnVsUGxhY2VtZW50ID0gcGxhY2VtZW50O1xuICBsZXQgbWlkZGxld2FyZURhdGEgPSB7fTtcbiAgbGV0IHJlc2V0Q291bnQgPSAwO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHZhbGlkTWlkZGxld2FyZS5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IHtcbiAgICAgIG5hbWUsXG4gICAgICBmblxuICAgIH0gPSB2YWxpZE1pZGRsZXdhcmVbaV07XG4gICAgY29uc3Qge1xuICAgICAgeDogbmV4dFgsXG4gICAgICB5OiBuZXh0WSxcbiAgICAgIGRhdGEsXG4gICAgICByZXNldFxuICAgIH0gPSBhd2FpdCBmbih7XG4gICAgICB4LFxuICAgICAgeSxcbiAgICAgIGluaXRpYWxQbGFjZW1lbnQ6IHBsYWNlbWVudCxcbiAgICAgIHBsYWNlbWVudDogc3RhdGVmdWxQbGFjZW1lbnQsXG4gICAgICBzdHJhdGVneSxcbiAgICAgIG1pZGRsZXdhcmVEYXRhLFxuICAgICAgcmVjdHMsXG4gICAgICBwbGF0Zm9ybSxcbiAgICAgIGVsZW1lbnRzOiB7XG4gICAgICAgIHJlZmVyZW5jZSxcbiAgICAgICAgZmxvYXRpbmdcbiAgICAgIH1cbiAgICB9KTtcbiAgICB4ID0gbmV4dFggIT0gbnVsbCA/IG5leHRYIDogeDtcbiAgICB5ID0gbmV4dFkgIT0gbnVsbCA/IG5leHRZIDogeTtcbiAgICBtaWRkbGV3YXJlRGF0YSA9IHtcbiAgICAgIC4uLm1pZGRsZXdhcmVEYXRhLFxuICAgICAgW25hbWVdOiB7XG4gICAgICAgIC4uLm1pZGRsZXdhcmVEYXRhW25hbWVdLFxuICAgICAgICAuLi5kYXRhXG4gICAgICB9XG4gICAgfTtcbiAgICBpZiAocmVzZXQgJiYgcmVzZXRDb3VudCA8PSA1MCkge1xuICAgICAgcmVzZXRDb3VudCsrO1xuICAgICAgaWYgKHR5cGVvZiByZXNldCA9PT0gJ29iamVjdCcpIHtcbiAgICAgICAgaWYgKHJlc2V0LnBsYWNlbWVudCkge1xuICAgICAgICAgIHN0YXRlZnVsUGxhY2VtZW50ID0gcmVzZXQucGxhY2VtZW50O1xuICAgICAgICB9XG4gICAgICAgIGlmIChyZXNldC5yZWN0cykge1xuICAgICAgICAgIHJlY3RzID0gcmVzZXQucmVjdHMgPT09IHRydWUgPyBhd2FpdCBwbGF0Zm9ybS5nZXRFbGVtZW50UmVjdHMoe1xuICAgICAgICAgICAgcmVmZXJlbmNlLFxuICAgICAgICAgICAgZmxvYXRpbmcsXG4gICAgICAgICAgICBzdHJhdGVneVxuICAgICAgICAgIH0pIDogcmVzZXQucmVjdHM7XG4gICAgICAgIH1cbiAgICAgICAgKHtcbiAgICAgICAgICB4LFxuICAgICAgICAgIHlcbiAgICAgICAgfSA9IGNvbXB1dGVDb29yZHNGcm9tUGxhY2VtZW50KHJlY3RzLCBzdGF0ZWZ1bFBsYWNlbWVudCwgcnRsKSk7XG4gICAgICB9XG4gICAgICBpID0gLTE7XG4gICAgfVxuICB9XG4gIHJldHVybiB7XG4gICAgeCxcbiAgICB5LFxuICAgIHBsYWNlbWVudDogc3RhdGVmdWxQbGFjZW1lbnQsXG4gICAgc3RyYXRlZ3ksXG4gICAgbWlkZGxld2FyZURhdGFcbiAgfTtcbn07XG5cbi8qKlxuICogUmVzb2x2ZXMgd2l0aCBhbiBvYmplY3Qgb2Ygb3ZlcmZsb3cgc2lkZSBvZmZzZXRzIHRoYXQgZGV0ZXJtaW5lIGhvdyBtdWNoIHRoZVxuICogZWxlbWVudCBpcyBvdmVyZmxvd2luZyBhIGdpdmVuIGNsaXBwaW5nIGJvdW5kYXJ5IG9uIGVhY2ggc2lkZS5cbiAqIC0gcG9zaXRpdmUgPSBvdmVyZmxvd2luZyB0aGUgYm91bmRhcnkgYnkgdGhhdCBudW1iZXIgb2YgcGl4ZWxzXG4gKiAtIG5lZ2F0aXZlID0gaG93IG1hbnkgcGl4ZWxzIGxlZnQgYmVmb3JlIGl0IHdpbGwgb3ZlcmZsb3dcbiAqIC0gMCA9IGxpZXMgZmx1c2ggd2l0aCB0aGUgYm91bmRhcnlcbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9kZXRlY3RPdmVyZmxvd1xuICovXG5hc3luYyBmdW5jdGlvbiBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwgb3B0aW9ucykge1xuICB2YXIgX2F3YWl0JHBsYXRmb3JtJGlzRWxlO1xuICBpZiAob3B0aW9ucyA9PT0gdm9pZCAwKSB7XG4gICAgb3B0aW9ucyA9IHt9O1xuICB9XG4gIGNvbnN0IHtcbiAgICB4LFxuICAgIHksXG4gICAgcGxhdGZvcm0sXG4gICAgcmVjdHMsXG4gICAgZWxlbWVudHMsXG4gICAgc3RyYXRlZ3lcbiAgfSA9IHN0YXRlO1xuICBjb25zdCB7XG4gICAgYm91bmRhcnkgPSAnY2xpcHBpbmdBbmNlc3RvcnMnLFxuICAgIHJvb3RCb3VuZGFyeSA9ICd2aWV3cG9ydCcsXG4gICAgZWxlbWVudENvbnRleHQgPSAnZmxvYXRpbmcnLFxuICAgIGFsdEJvdW5kYXJ5ID0gZmFsc2UsXG4gICAgcGFkZGluZyA9IDBcbiAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKTtcbiAgY29uc3QgcGFkZGluZ09iamVjdCA9IGdldFBhZGRpbmdPYmplY3QocGFkZGluZyk7XG4gIGNvbnN0IGFsdENvbnRleHQgPSBlbGVtZW50Q29udGV4dCA9PT0gJ2Zsb2F0aW5nJyA/ICdyZWZlcmVuY2UnIDogJ2Zsb2F0aW5nJztcbiAgY29uc3QgZWxlbWVudCA9IGVsZW1lbnRzW2FsdEJvdW5kYXJ5ID8gYWx0Q29udGV4dCA6IGVsZW1lbnRDb250ZXh0XTtcbiAgY29uc3QgY2xpcHBpbmdDbGllbnRSZWN0ID0gcmVjdFRvQ2xpZW50UmVjdChhd2FpdCBwbGF0Zm9ybS5nZXRDbGlwcGluZ1JlY3Qoe1xuICAgIGVsZW1lbnQ6ICgoX2F3YWl0JHBsYXRmb3JtJGlzRWxlID0gYXdhaXQgKHBsYXRmb3JtLmlzRWxlbWVudCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNFbGVtZW50KGVsZW1lbnQpKSkgIT0gbnVsbCA/IF9hd2FpdCRwbGF0Zm9ybSRpc0VsZSA6IHRydWUpID8gZWxlbWVudCA6IGVsZW1lbnQuY29udGV4dEVsZW1lbnQgfHwgKGF3YWl0IChwbGF0Zm9ybS5nZXREb2N1bWVudEVsZW1lbnQgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmdldERvY3VtZW50RWxlbWVudChlbGVtZW50cy5mbG9hdGluZykpKSxcbiAgICBib3VuZGFyeSxcbiAgICByb290Qm91bmRhcnksXG4gICAgc3RyYXRlZ3lcbiAgfSkpO1xuICBjb25zdCByZWN0ID0gZWxlbWVudENvbnRleHQgPT09ICdmbG9hdGluZycgPyB7XG4gICAgeCxcbiAgICB5LFxuICAgIHdpZHRoOiByZWN0cy5mbG9hdGluZy53aWR0aCxcbiAgICBoZWlnaHQ6IHJlY3RzLmZsb2F0aW5nLmhlaWdodFxuICB9IDogcmVjdHMucmVmZXJlbmNlO1xuICBjb25zdCBvZmZzZXRQYXJlbnQgPSBhd2FpdCAocGxhdGZvcm0uZ2V0T2Zmc2V0UGFyZW50ID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5nZXRPZmZzZXRQYXJlbnQoZWxlbWVudHMuZmxvYXRpbmcpKTtcbiAgY29uc3Qgb2Zmc2V0U2NhbGUgPSAoYXdhaXQgKHBsYXRmb3JtLmlzRWxlbWVudCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNFbGVtZW50KG9mZnNldFBhcmVudCkpKSA/IChhd2FpdCAocGxhdGZvcm0uZ2V0U2NhbGUgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmdldFNjYWxlKG9mZnNldFBhcmVudCkpKSB8fCB7XG4gICAgeDogMSxcbiAgICB5OiAxXG4gIH0gOiB7XG4gICAgeDogMSxcbiAgICB5OiAxXG4gIH07XG4gIGNvbnN0IGVsZW1lbnRDbGllbnRSZWN0ID0gcmVjdFRvQ2xpZW50UmVjdChwbGF0Zm9ybS5jb252ZXJ0T2Zmc2V0UGFyZW50UmVsYXRpdmVSZWN0VG9WaWV3cG9ydFJlbGF0aXZlUmVjdCA/IGF3YWl0IHBsYXRmb3JtLmNvbnZlcnRPZmZzZXRQYXJlbnRSZWxhdGl2ZVJlY3RUb1ZpZXdwb3J0UmVsYXRpdmVSZWN0KHtcbiAgICBlbGVtZW50cyxcbiAgICByZWN0LFxuICAgIG9mZnNldFBhcmVudCxcbiAgICBzdHJhdGVneVxuICB9KSA6IHJlY3QpO1xuICByZXR1cm4ge1xuICAgIHRvcDogKGNsaXBwaW5nQ2xpZW50UmVjdC50b3AgLSBlbGVtZW50Q2xpZW50UmVjdC50b3AgKyBwYWRkaW5nT2JqZWN0LnRvcCkgLyBvZmZzZXRTY2FsZS55LFxuICAgIGJvdHRvbTogKGVsZW1lbnRDbGllbnRSZWN0LmJvdHRvbSAtIGNsaXBwaW5nQ2xpZW50UmVjdC5ib3R0b20gKyBwYWRkaW5nT2JqZWN0LmJvdHRvbSkgLyBvZmZzZXRTY2FsZS55LFxuICAgIGxlZnQ6IChjbGlwcGluZ0NsaWVudFJlY3QubGVmdCAtIGVsZW1lbnRDbGllbnRSZWN0LmxlZnQgKyBwYWRkaW5nT2JqZWN0LmxlZnQpIC8gb2Zmc2V0U2NhbGUueCxcbiAgICByaWdodDogKGVsZW1lbnRDbGllbnRSZWN0LnJpZ2h0IC0gY2xpcHBpbmdDbGllbnRSZWN0LnJpZ2h0ICsgcGFkZGluZ09iamVjdC5yaWdodCkgLyBvZmZzZXRTY2FsZS54XG4gIH07XG59XG5cbi8qKlxuICogUHJvdmlkZXMgZGF0YSB0byBwb3NpdGlvbiBhbiBpbm5lciBlbGVtZW50IG9mIHRoZSBmbG9hdGluZyBlbGVtZW50IHNvIHRoYXQgaXRcbiAqIGFwcGVhcnMgY2VudGVyZWQgdG8gdGhlIHJlZmVyZW5jZSBlbGVtZW50LlxuICogQHNlZSBodHRwczovL2Zsb2F0aW5nLXVpLmNvbS9kb2NzL2Fycm93XG4gKi9cbmNvbnN0IGFycm93ID0gb3B0aW9ucyA9PiAoe1xuICBuYW1lOiAnYXJyb3cnLFxuICBvcHRpb25zLFxuICBhc3luYyBmbihzdGF0ZSkge1xuICAgIGNvbnN0IHtcbiAgICAgIHgsXG4gICAgICB5LFxuICAgICAgcGxhY2VtZW50LFxuICAgICAgcmVjdHMsXG4gICAgICBwbGF0Zm9ybSxcbiAgICAgIGVsZW1lbnRzLFxuICAgICAgbWlkZGxld2FyZURhdGFcbiAgICB9ID0gc3RhdGU7XG4gICAgLy8gU2luY2UgYGVsZW1lbnRgIGlzIHJlcXVpcmVkLCB3ZSBkb24ndCBQYXJ0aWFsPD4gdGhlIHR5cGUuXG4gICAgY29uc3Qge1xuICAgICAgZWxlbWVudCxcbiAgICAgIHBhZGRpbmcgPSAwXG4gICAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKSB8fCB7fTtcbiAgICBpZiAoZWxlbWVudCA9PSBudWxsKSB7XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuICAgIGNvbnN0IHBhZGRpbmdPYmplY3QgPSBnZXRQYWRkaW5nT2JqZWN0KHBhZGRpbmcpO1xuICAgIGNvbnN0IGNvb3JkcyA9IHtcbiAgICAgIHgsXG4gICAgICB5XG4gICAgfTtcbiAgICBjb25zdCBheGlzID0gZ2V0QWxpZ25tZW50QXhpcyhwbGFjZW1lbnQpO1xuICAgIGNvbnN0IGxlbmd0aCA9IGdldEF4aXNMZW5ndGgoYXhpcyk7XG4gICAgY29uc3QgYXJyb3dEaW1lbnNpb25zID0gYXdhaXQgcGxhdGZvcm0uZ2V0RGltZW5zaW9ucyhlbGVtZW50KTtcbiAgICBjb25zdCBpc1lBeGlzID0gYXhpcyA9PT0gJ3knO1xuICAgIGNvbnN0IG1pblByb3AgPSBpc1lBeGlzID8gJ3RvcCcgOiAnbGVmdCc7XG4gICAgY29uc3QgbWF4UHJvcCA9IGlzWUF4aXMgPyAnYm90dG9tJyA6ICdyaWdodCc7XG4gICAgY29uc3QgY2xpZW50UHJvcCA9IGlzWUF4aXMgPyAnY2xpZW50SGVpZ2h0JyA6ICdjbGllbnRXaWR0aCc7XG4gICAgY29uc3QgZW5kRGlmZiA9IHJlY3RzLnJlZmVyZW5jZVtsZW5ndGhdICsgcmVjdHMucmVmZXJlbmNlW2F4aXNdIC0gY29vcmRzW2F4aXNdIC0gcmVjdHMuZmxvYXRpbmdbbGVuZ3RoXTtcbiAgICBjb25zdCBzdGFydERpZmYgPSBjb29yZHNbYXhpc10gLSByZWN0cy5yZWZlcmVuY2VbYXhpc107XG4gICAgY29uc3QgYXJyb3dPZmZzZXRQYXJlbnQgPSBhd2FpdCAocGxhdGZvcm0uZ2V0T2Zmc2V0UGFyZW50ID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5nZXRPZmZzZXRQYXJlbnQoZWxlbWVudCkpO1xuICAgIGxldCBjbGllbnRTaXplID0gYXJyb3dPZmZzZXRQYXJlbnQgPyBhcnJvd09mZnNldFBhcmVudFtjbGllbnRQcm9wXSA6IDA7XG5cbiAgICAvLyBET00gcGxhdGZvcm0gY2FuIHJldHVybiBgd2luZG93YCBhcyB0aGUgYG9mZnNldFBhcmVudGAuXG4gICAgaWYgKCFjbGllbnRTaXplIHx8ICEoYXdhaXQgKHBsYXRmb3JtLmlzRWxlbWVudCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNFbGVtZW50KGFycm93T2Zmc2V0UGFyZW50KSkpKSB7XG4gICAgICBjbGllbnRTaXplID0gZWxlbWVudHMuZmxvYXRpbmdbY2xpZW50UHJvcF0gfHwgcmVjdHMuZmxvYXRpbmdbbGVuZ3RoXTtcbiAgICB9XG4gICAgY29uc3QgY2VudGVyVG9SZWZlcmVuY2UgPSBlbmREaWZmIC8gMiAtIHN0YXJ0RGlmZiAvIDI7XG5cbiAgICAvLyBJZiB0aGUgcGFkZGluZyBpcyBsYXJnZSBlbm91Z2ggdGhhdCBpdCBjYXVzZXMgdGhlIGFycm93IHRvIG5vIGxvbmdlciBiZVxuICAgIC8vIGNlbnRlcmVkLCBtb2RpZnkgdGhlIHBhZGRpbmcgc28gdGhhdCBpdCBpcyBjZW50ZXJlZC5cbiAgICBjb25zdCBsYXJnZXN0UG9zc2libGVQYWRkaW5nID0gY2xpZW50U2l6ZSAvIDIgLSBhcnJvd0RpbWVuc2lvbnNbbGVuZ3RoXSAvIDIgLSAxO1xuICAgIGNvbnN0IG1pblBhZGRpbmcgPSBtaW4ocGFkZGluZ09iamVjdFttaW5Qcm9wXSwgbGFyZ2VzdFBvc3NpYmxlUGFkZGluZyk7XG4gICAgY29uc3QgbWF4UGFkZGluZyA9IG1pbihwYWRkaW5nT2JqZWN0W21heFByb3BdLCBsYXJnZXN0UG9zc2libGVQYWRkaW5nKTtcblxuICAgIC8vIE1ha2Ugc3VyZSB0aGUgYXJyb3cgZG9lc24ndCBvdmVyZmxvdyB0aGUgZmxvYXRpbmcgZWxlbWVudCBpZiB0aGUgY2VudGVyXG4gICAgLy8gcG9pbnQgaXMgb3V0c2lkZSB0aGUgZmxvYXRpbmcgZWxlbWVudCdzIGJvdW5kcy5cbiAgICBjb25zdCBtaW4kMSA9IG1pblBhZGRpbmc7XG4gICAgY29uc3QgbWF4ID0gY2xpZW50U2l6ZSAtIGFycm93RGltZW5zaW9uc1tsZW5ndGhdIC0gbWF4UGFkZGluZztcbiAgICBjb25zdCBjZW50ZXIgPSBjbGllbnRTaXplIC8gMiAtIGFycm93RGltZW5zaW9uc1tsZW5ndGhdIC8gMiArIGNlbnRlclRvUmVmZXJlbmNlO1xuICAgIGNvbnN0IG9mZnNldCA9IGNsYW1wKG1pbiQxLCBjZW50ZXIsIG1heCk7XG5cbiAgICAvLyBJZiB0aGUgcmVmZXJlbmNlIGlzIHNtYWxsIGVub3VnaCB0aGF0IHRoZSBhcnJvdydzIHBhZGRpbmcgY2F1c2VzIGl0IHRvXG4gICAgLy8gdG8gcG9pbnQgdG8gbm90aGluZyBmb3IgYW4gYWxpZ25lZCBwbGFjZW1lbnQsIGFkanVzdCB0aGUgb2Zmc2V0IG9mIHRoZVxuICAgIC8vIGZsb2F0aW5nIGVsZW1lbnQgaXRzZWxmLiBUbyBlbnN1cmUgYHNoaWZ0KClgIGNvbnRpbnVlcyB0byB0YWtlIGFjdGlvbixcbiAgICAvLyBhIHNpbmdsZSByZXNldCBpcyBwZXJmb3JtZWQgd2hlbiB0aGlzIGlzIHRydWUuXG4gICAgY29uc3Qgc2hvdWxkQWRkT2Zmc2V0ID0gIW1pZGRsZXdhcmVEYXRhLmFycm93ICYmIGdldEFsaWdubWVudChwbGFjZW1lbnQpICE9IG51bGwgJiYgY2VudGVyICE9PSBvZmZzZXQgJiYgcmVjdHMucmVmZXJlbmNlW2xlbmd0aF0gLyAyIC0gKGNlbnRlciA8IG1pbiQxID8gbWluUGFkZGluZyA6IG1heFBhZGRpbmcpIC0gYXJyb3dEaW1lbnNpb25zW2xlbmd0aF0gLyAyIDwgMDtcbiAgICBjb25zdCBhbGlnbm1lbnRPZmZzZXQgPSBzaG91bGRBZGRPZmZzZXQgPyBjZW50ZXIgPCBtaW4kMSA/IGNlbnRlciAtIG1pbiQxIDogY2VudGVyIC0gbWF4IDogMDtcbiAgICByZXR1cm4ge1xuICAgICAgW2F4aXNdOiBjb29yZHNbYXhpc10gKyBhbGlnbm1lbnRPZmZzZXQsXG4gICAgICBkYXRhOiB7XG4gICAgICAgIFtheGlzXTogb2Zmc2V0LFxuICAgICAgICBjZW50ZXJPZmZzZXQ6IGNlbnRlciAtIG9mZnNldCAtIGFsaWdubWVudE9mZnNldCxcbiAgICAgICAgLi4uKHNob3VsZEFkZE9mZnNldCAmJiB7XG4gICAgICAgICAgYWxpZ25tZW50T2Zmc2V0XG4gICAgICAgIH0pXG4gICAgICB9LFxuICAgICAgcmVzZXQ6IHNob3VsZEFkZE9mZnNldFxuICAgIH07XG4gIH1cbn0pO1xuXG5mdW5jdGlvbiBnZXRQbGFjZW1lbnRMaXN0KGFsaWdubWVudCwgYXV0b0FsaWdubWVudCwgYWxsb3dlZFBsYWNlbWVudHMpIHtcbiAgY29uc3QgYWxsb3dlZFBsYWNlbWVudHNTb3J0ZWRCeUFsaWdubWVudCA9IGFsaWdubWVudCA/IFsuLi5hbGxvd2VkUGxhY2VtZW50cy5maWx0ZXIocGxhY2VtZW50ID0+IGdldEFsaWdubWVudChwbGFjZW1lbnQpID09PSBhbGlnbm1lbnQpLCAuLi5hbGxvd2VkUGxhY2VtZW50cy5maWx0ZXIocGxhY2VtZW50ID0+IGdldEFsaWdubWVudChwbGFjZW1lbnQpICE9PSBhbGlnbm1lbnQpXSA6IGFsbG93ZWRQbGFjZW1lbnRzLmZpbHRlcihwbGFjZW1lbnQgPT4gZ2V0U2lkZShwbGFjZW1lbnQpID09PSBwbGFjZW1lbnQpO1xuICByZXR1cm4gYWxsb3dlZFBsYWNlbWVudHNTb3J0ZWRCeUFsaWdubWVudC5maWx0ZXIocGxhY2VtZW50ID0+IHtcbiAgICBpZiAoYWxpZ25tZW50KSB7XG4gICAgICByZXR1cm4gZ2V0QWxpZ25tZW50KHBsYWNlbWVudCkgPT09IGFsaWdubWVudCB8fCAoYXV0b0FsaWdubWVudCA/IGdldE9wcG9zaXRlQWxpZ25tZW50UGxhY2VtZW50KHBsYWNlbWVudCkgIT09IHBsYWNlbWVudCA6IGZhbHNlKTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG4gIH0pO1xufVxuLyoqXG4gKiBPcHRpbWl6ZXMgdGhlIHZpc2liaWxpdHkgb2YgdGhlIGZsb2F0aW5nIGVsZW1lbnQgYnkgY2hvb3NpbmcgdGhlIHBsYWNlbWVudFxuICogdGhhdCBoYXMgdGhlIG1vc3Qgc3BhY2UgYXZhaWxhYmxlIGF1dG9tYXRpY2FsbHksIHdpdGhvdXQgbmVlZGluZyB0byBzcGVjaWZ5IGFcbiAqIHByZWZlcnJlZCBwbGFjZW1lbnQuIEFsdGVybmF0aXZlIHRvIGBmbGlwYC5cbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9hdXRvUGxhY2VtZW50XG4gKi9cbmNvbnN0IGF1dG9QbGFjZW1lbnQgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICBpZiAob3B0aW9ucyA9PT0gdm9pZCAwKSB7XG4gICAgb3B0aW9ucyA9IHt9O1xuICB9XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ2F1dG9QbGFjZW1lbnQnLFxuICAgIG9wdGlvbnMsXG4gICAgYXN5bmMgZm4oc3RhdGUpIHtcbiAgICAgIHZhciBfbWlkZGxld2FyZURhdGEkYXV0b1AsIF9taWRkbGV3YXJlRGF0YSRhdXRvUDIsIF9wbGFjZW1lbnRzVGhhdEZpdE9uRTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcmVjdHMsXG4gICAgICAgIG1pZGRsZXdhcmVEYXRhLFxuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIHBsYXRmb3JtLFxuICAgICAgICBlbGVtZW50c1xuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBjcm9zc0F4aXMgPSBmYWxzZSxcbiAgICAgICAgYWxpZ25tZW50LFxuICAgICAgICBhbGxvd2VkUGxhY2VtZW50cyA9IHBsYWNlbWVudHMsXG4gICAgICAgIGF1dG9BbGlnbm1lbnQgPSB0cnVlLFxuICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnNcbiAgICAgIH0gPSBldmFsdWF0ZShvcHRpb25zLCBzdGF0ZSk7XG4gICAgICBjb25zdCBwbGFjZW1lbnRzJDEgPSBhbGlnbm1lbnQgIT09IHVuZGVmaW5lZCB8fCBhbGxvd2VkUGxhY2VtZW50cyA9PT0gcGxhY2VtZW50cyA/IGdldFBsYWNlbWVudExpc3QoYWxpZ25tZW50IHx8IG51bGwsIGF1dG9BbGlnbm1lbnQsIGFsbG93ZWRQbGFjZW1lbnRzKSA6IGFsbG93ZWRQbGFjZW1lbnRzO1xuICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwgZGV0ZWN0T3ZlcmZsb3dPcHRpb25zKTtcbiAgICAgIGNvbnN0IGN1cnJlbnRJbmRleCA9ICgoX21pZGRsZXdhcmVEYXRhJGF1dG9QID0gbWlkZGxld2FyZURhdGEuYXV0b1BsYWNlbWVudCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9taWRkbGV3YXJlRGF0YSRhdXRvUC5pbmRleCkgfHwgMDtcbiAgICAgIGNvbnN0IGN1cnJlbnRQbGFjZW1lbnQgPSBwbGFjZW1lbnRzJDFbY3VycmVudEluZGV4XTtcbiAgICAgIGlmIChjdXJyZW50UGxhY2VtZW50ID09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgfVxuICAgICAgY29uc3QgYWxpZ25tZW50U2lkZXMgPSBnZXRBbGlnbm1lbnRTaWRlcyhjdXJyZW50UGxhY2VtZW50LCByZWN0cywgYXdhaXQgKHBsYXRmb3JtLmlzUlRMID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5pc1JUTChlbGVtZW50cy5mbG9hdGluZykpKTtcblxuICAgICAgLy8gTWFrZSBgY29tcHV0ZUNvb3Jkc2Agc3RhcnQgZnJvbSB0aGUgcmlnaHQgcGxhY2UuXG4gICAgICBpZiAocGxhY2VtZW50ICE9PSBjdXJyZW50UGxhY2VtZW50KSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgcmVzZXQ6IHtcbiAgICAgICAgICAgIHBsYWNlbWVudDogcGxhY2VtZW50cyQxWzBdXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgY29uc3QgY3VycmVudE92ZXJmbG93cyA9IFtvdmVyZmxvd1tnZXRTaWRlKGN1cnJlbnRQbGFjZW1lbnQpXSwgb3ZlcmZsb3dbYWxpZ25tZW50U2lkZXNbMF1dLCBvdmVyZmxvd1thbGlnbm1lbnRTaWRlc1sxXV1dO1xuICAgICAgY29uc3QgYWxsT3ZlcmZsb3dzID0gWy4uLigoKF9taWRkbGV3YXJlRGF0YSRhdXRvUDIgPSBtaWRkbGV3YXJlRGF0YS5hdXRvUGxhY2VtZW50KSA9PSBudWxsID8gdm9pZCAwIDogX21pZGRsZXdhcmVEYXRhJGF1dG9QMi5vdmVyZmxvd3MpIHx8IFtdKSwge1xuICAgICAgICBwbGFjZW1lbnQ6IGN1cnJlbnRQbGFjZW1lbnQsXG4gICAgICAgIG92ZXJmbG93czogY3VycmVudE92ZXJmbG93c1xuICAgICAgfV07XG4gICAgICBjb25zdCBuZXh0UGxhY2VtZW50ID0gcGxhY2VtZW50cyQxW2N1cnJlbnRJbmRleCArIDFdO1xuXG4gICAgICAvLyBUaGVyZSBhcmUgbW9yZSBwbGFjZW1lbnRzIHRvIGNoZWNrLlxuICAgICAgaWYgKG5leHRQbGFjZW1lbnQpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICBpbmRleDogY3VycmVudEluZGV4ICsgMSxcbiAgICAgICAgICAgIG92ZXJmbG93czogYWxsT3ZlcmZsb3dzXG4gICAgICAgICAgfSxcbiAgICAgICAgICByZXNldDoge1xuICAgICAgICAgICAgcGxhY2VtZW50OiBuZXh0UGxhY2VtZW50XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgY29uc3QgcGxhY2VtZW50c1NvcnRlZEJ5TW9zdFNwYWNlID0gYWxsT3ZlcmZsb3dzLm1hcChkID0+IHtcbiAgICAgICAgY29uc3QgYWxpZ25tZW50ID0gZ2V0QWxpZ25tZW50KGQucGxhY2VtZW50KTtcbiAgICAgICAgcmV0dXJuIFtkLnBsYWNlbWVudCwgYWxpZ25tZW50ICYmIGNyb3NzQXhpcyA/XG4gICAgICAgIC8vIENoZWNrIGFsb25nIHRoZSBtYWluQXhpcyBhbmQgbWFpbiBjcm9zc0F4aXMgc2lkZS5cbiAgICAgICAgZC5vdmVyZmxvd3Muc2xpY2UoMCwgMikucmVkdWNlKChhY2MsIHYpID0+IGFjYyArIHYsIDApIDpcbiAgICAgICAgLy8gQ2hlY2sgb25seSB0aGUgbWFpbkF4aXMuXG4gICAgICAgIGQub3ZlcmZsb3dzWzBdLCBkLm92ZXJmbG93c107XG4gICAgICB9KS5zb3J0KChhLCBiKSA9PiBhWzFdIC0gYlsxXSk7XG4gICAgICBjb25zdCBwbGFjZW1lbnRzVGhhdEZpdE9uRWFjaFNpZGUgPSBwbGFjZW1lbnRzU29ydGVkQnlNb3N0U3BhY2UuZmlsdGVyKGQgPT4gZFsyXS5zbGljZSgwLFxuICAgICAgLy8gQWxpZ25lZCBwbGFjZW1lbnRzIHNob3VsZCBub3QgY2hlY2sgdGhlaXIgb3Bwb3NpdGUgY3Jvc3NBeGlzXG4gICAgICAvLyBzaWRlLlxuICAgICAgZ2V0QWxpZ25tZW50KGRbMF0pID8gMiA6IDMpLmV2ZXJ5KHYgPT4gdiA8PSAwKSk7XG4gICAgICBjb25zdCByZXNldFBsYWNlbWVudCA9ICgoX3BsYWNlbWVudHNUaGF0Rml0T25FID0gcGxhY2VtZW50c1RoYXRGaXRPbkVhY2hTaWRlWzBdKSA9PSBudWxsID8gdm9pZCAwIDogX3BsYWNlbWVudHNUaGF0Rml0T25FWzBdKSB8fCBwbGFjZW1lbnRzU29ydGVkQnlNb3N0U3BhY2VbMF1bMF07XG4gICAgICBpZiAocmVzZXRQbGFjZW1lbnQgIT09IHBsYWNlbWVudCkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgIGluZGV4OiBjdXJyZW50SW5kZXggKyAxLFxuICAgICAgICAgICAgb3ZlcmZsb3dzOiBhbGxPdmVyZmxvd3NcbiAgICAgICAgICB9LFxuICAgICAgICAgIHJlc2V0OiB7XG4gICAgICAgICAgICBwbGFjZW1lbnQ6IHJlc2V0UGxhY2VtZW50XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgcmV0dXJuIHt9O1xuICAgIH1cbiAgfTtcbn07XG5cbi8qKlxuICogT3B0aW1pemVzIHRoZSB2aXNpYmlsaXR5IG9mIHRoZSBmbG9hdGluZyBlbGVtZW50IGJ5IGZsaXBwaW5nIHRoZSBgcGxhY2VtZW50YFxuICogaW4gb3JkZXIgdG8ga2VlcCBpdCBpbiB2aWV3IHdoZW4gdGhlIHByZWZlcnJlZCBwbGFjZW1lbnQocykgd2lsbCBvdmVyZmxvdyB0aGVcbiAqIGNsaXBwaW5nIGJvdW5kYXJ5LiBBbHRlcm5hdGl2ZSB0byBgYXV0b1BsYWNlbWVudGAuXG4gKiBAc2VlIGh0dHBzOi8vZmxvYXRpbmctdWkuY29tL2RvY3MvZmxpcFxuICovXG5jb25zdCBmbGlwID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG5hbWU6ICdmbGlwJyxcbiAgICBvcHRpb25zLFxuICAgIGFzeW5jIGZuKHN0YXRlKSB7XG4gICAgICB2YXIgX21pZGRsZXdhcmVEYXRhJGFycm93LCBfbWlkZGxld2FyZURhdGEkZmxpcDtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcGxhY2VtZW50LFxuICAgICAgICBtaWRkbGV3YXJlRGF0YSxcbiAgICAgICAgcmVjdHMsXG4gICAgICAgIGluaXRpYWxQbGFjZW1lbnQsXG4gICAgICAgIHBsYXRmb3JtLFxuICAgICAgICBlbGVtZW50c1xuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBtYWluQXhpczogY2hlY2tNYWluQXhpcyA9IHRydWUsXG4gICAgICAgIGNyb3NzQXhpczogY2hlY2tDcm9zc0F4aXMgPSB0cnVlLFxuICAgICAgICBmYWxsYmFja1BsYWNlbWVudHM6IHNwZWNpZmllZEZhbGxiYWNrUGxhY2VtZW50cyxcbiAgICAgICAgZmFsbGJhY2tTdHJhdGVneSA9ICdiZXN0Rml0JyxcbiAgICAgICAgZmFsbGJhY2tBeGlzU2lkZURpcmVjdGlvbiA9ICdub25lJyxcbiAgICAgICAgZmxpcEFsaWdubWVudCA9IHRydWUsXG4gICAgICAgIC4uLmRldGVjdE92ZXJmbG93T3B0aW9uc1xuICAgICAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKTtcblxuICAgICAgLy8gSWYgYSByZXNldCBieSB0aGUgYXJyb3cgd2FzIGNhdXNlZCBkdWUgdG8gYW4gYWxpZ25tZW50IG9mZnNldCBiZWluZ1xuICAgICAgLy8gYWRkZWQsIHdlIHNob3VsZCBza2lwIGFueSBsb2dpYyBub3cgc2luY2UgYGZsaXAoKWAgaGFzIGFscmVhZHkgZG9uZSBpdHNcbiAgICAgIC8vIHdvcmsuXG4gICAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vZmxvYXRpbmctdWkvZmxvYXRpbmctdWkvaXNzdWVzLzI1NDkjaXNzdWVjb21tZW50LTE3MTk2MDE2NDNcbiAgICAgIGlmICgoX21pZGRsZXdhcmVEYXRhJGFycm93ID0gbWlkZGxld2FyZURhdGEuYXJyb3cpICE9IG51bGwgJiYgX21pZGRsZXdhcmVEYXRhJGFycm93LmFsaWdubWVudE9mZnNldCkge1xuICAgICAgICByZXR1cm4ge307XG4gICAgICB9XG4gICAgICBjb25zdCBzaWRlID0gZ2V0U2lkZShwbGFjZW1lbnQpO1xuICAgICAgY29uc3QgaW5pdGlhbFNpZGVBeGlzID0gZ2V0U2lkZUF4aXMoaW5pdGlhbFBsYWNlbWVudCk7XG4gICAgICBjb25zdCBpc0Jhc2VQbGFjZW1lbnQgPSBnZXRTaWRlKGluaXRpYWxQbGFjZW1lbnQpID09PSBpbml0aWFsUGxhY2VtZW50O1xuICAgICAgY29uc3QgcnRsID0gYXdhaXQgKHBsYXRmb3JtLmlzUlRMID09IG51bGwgPyB2b2lkIDAgOiBwbGF0Zm9ybS5pc1JUTChlbGVtZW50cy5mbG9hdGluZykpO1xuICAgICAgY29uc3QgZmFsbGJhY2tQbGFjZW1lbnRzID0gc3BlY2lmaWVkRmFsbGJhY2tQbGFjZW1lbnRzIHx8IChpc0Jhc2VQbGFjZW1lbnQgfHwgIWZsaXBBbGlnbm1lbnQgPyBbZ2V0T3Bwb3NpdGVQbGFjZW1lbnQoaW5pdGlhbFBsYWNlbWVudCldIDogZ2V0RXhwYW5kZWRQbGFjZW1lbnRzKGluaXRpYWxQbGFjZW1lbnQpKTtcbiAgICAgIGNvbnN0IGhhc0ZhbGxiYWNrQXhpc1NpZGVEaXJlY3Rpb24gPSBmYWxsYmFja0F4aXNTaWRlRGlyZWN0aW9uICE9PSAnbm9uZSc7XG4gICAgICBpZiAoIXNwZWNpZmllZEZhbGxiYWNrUGxhY2VtZW50cyAmJiBoYXNGYWxsYmFja0F4aXNTaWRlRGlyZWN0aW9uKSB7XG4gICAgICAgIGZhbGxiYWNrUGxhY2VtZW50cy5wdXNoKC4uLmdldE9wcG9zaXRlQXhpc1BsYWNlbWVudHMoaW5pdGlhbFBsYWNlbWVudCwgZmxpcEFsaWdubWVudCwgZmFsbGJhY2tBeGlzU2lkZURpcmVjdGlvbiwgcnRsKSk7XG4gICAgICB9XG4gICAgICBjb25zdCBwbGFjZW1lbnRzID0gW2luaXRpYWxQbGFjZW1lbnQsIC4uLmZhbGxiYWNrUGxhY2VtZW50c107XG4gICAgICBjb25zdCBvdmVyZmxvdyA9IGF3YWl0IGRldGVjdE92ZXJmbG93KHN0YXRlLCBkZXRlY3RPdmVyZmxvd09wdGlvbnMpO1xuICAgICAgY29uc3Qgb3ZlcmZsb3dzID0gW107XG4gICAgICBsZXQgb3ZlcmZsb3dzRGF0YSA9ICgoX21pZGRsZXdhcmVEYXRhJGZsaXAgPSBtaWRkbGV3YXJlRGF0YS5mbGlwKSA9PSBudWxsID8gdm9pZCAwIDogX21pZGRsZXdhcmVEYXRhJGZsaXAub3ZlcmZsb3dzKSB8fCBbXTtcbiAgICAgIGlmIChjaGVja01haW5BeGlzKSB7XG4gICAgICAgIG92ZXJmbG93cy5wdXNoKG92ZXJmbG93W3NpZGVdKTtcbiAgICAgIH1cbiAgICAgIGlmIChjaGVja0Nyb3NzQXhpcykge1xuICAgICAgICBjb25zdCBzaWRlcyA9IGdldEFsaWdubWVudFNpZGVzKHBsYWNlbWVudCwgcmVjdHMsIHJ0bCk7XG4gICAgICAgIG92ZXJmbG93cy5wdXNoKG92ZXJmbG93W3NpZGVzWzBdXSwgb3ZlcmZsb3dbc2lkZXNbMV1dKTtcbiAgICAgIH1cbiAgICAgIG92ZXJmbG93c0RhdGEgPSBbLi4ub3ZlcmZsb3dzRGF0YSwge1xuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIG92ZXJmbG93c1xuICAgICAgfV07XG5cbiAgICAgIC8vIE9uZSBvciBtb3JlIHNpZGVzIGlzIG92ZXJmbG93aW5nLlxuICAgICAgaWYgKCFvdmVyZmxvd3MuZXZlcnkoc2lkZSA9PiBzaWRlIDw9IDApKSB7XG4gICAgICAgIHZhciBfbWlkZGxld2FyZURhdGEkZmxpcDIsIF9vdmVyZmxvd3NEYXRhJGZpbHRlcjtcbiAgICAgICAgY29uc3QgbmV4dEluZGV4ID0gKCgoX21pZGRsZXdhcmVEYXRhJGZsaXAyID0gbWlkZGxld2FyZURhdGEuZmxpcCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9taWRkbGV3YXJlRGF0YSRmbGlwMi5pbmRleCkgfHwgMCkgKyAxO1xuICAgICAgICBjb25zdCBuZXh0UGxhY2VtZW50ID0gcGxhY2VtZW50c1tuZXh0SW5kZXhdO1xuICAgICAgICBpZiAobmV4dFBsYWNlbWVudCkge1xuICAgICAgICAgIGNvbnN0IGlnbm9yZUNyb3NzQXhpc092ZXJmbG93ID0gY2hlY2tDcm9zc0F4aXMgPT09ICdhbGlnbm1lbnQnID8gaW5pdGlhbFNpZGVBeGlzICE9PSBnZXRTaWRlQXhpcyhuZXh0UGxhY2VtZW50KSA6IGZhbHNlO1xuICAgICAgICAgIGlmICghaWdub3JlQ3Jvc3NBeGlzT3ZlcmZsb3cgfHxcbiAgICAgICAgICAvLyBXZSBsZWF2ZSB0aGUgY3VycmVudCBtYWluIGF4aXMgb25seSBpZiBldmVyeSBwbGFjZW1lbnQgb24gdGhhdCBheGlzXG4gICAgICAgICAgLy8gb3ZlcmZsb3dzIHRoZSBtYWluIGF4aXMuXG4gICAgICAgICAgb3ZlcmZsb3dzRGF0YS5ldmVyeShkID0+IGdldFNpZGVBeGlzKGQucGxhY2VtZW50KSA9PT0gaW5pdGlhbFNpZGVBeGlzID8gZC5vdmVyZmxvd3NbMF0gPiAwIDogdHJ1ZSkpIHtcbiAgICAgICAgICAgIC8vIFRyeSBuZXh0IHBsYWNlbWVudCBhbmQgcmUtcnVuIHRoZSBsaWZlY3ljbGUuXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAgICAgaW5kZXg6IG5leHRJbmRleCxcbiAgICAgICAgICAgICAgICBvdmVyZmxvd3M6IG92ZXJmbG93c0RhdGFcbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgcmVzZXQ6IHtcbiAgICAgICAgICAgICAgICBwbGFjZW1lbnQ6IG5leHRQbGFjZW1lbnRcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBGaXJzdCwgZmluZCB0aGUgY2FuZGlkYXRlcyB0aGF0IGZpdCBvbiB0aGUgbWFpbkF4aXMgc2lkZSBvZiBvdmVyZmxvdyxcbiAgICAgICAgLy8gdGhlbiBmaW5kIHRoZSBwbGFjZW1lbnQgdGhhdCBmaXRzIHRoZSBiZXN0IG9uIHRoZSBtYWluIGNyb3NzQXhpcyBzaWRlLlxuICAgICAgICBsZXQgcmVzZXRQbGFjZW1lbnQgPSAoX292ZXJmbG93c0RhdGEkZmlsdGVyID0gb3ZlcmZsb3dzRGF0YS5maWx0ZXIoZCA9PiBkLm92ZXJmbG93c1swXSA8PSAwKS5zb3J0KChhLCBiKSA9PiBhLm92ZXJmbG93c1sxXSAtIGIub3ZlcmZsb3dzWzFdKVswXSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9vdmVyZmxvd3NEYXRhJGZpbHRlci5wbGFjZW1lbnQ7XG5cbiAgICAgICAgLy8gT3RoZXJ3aXNlIGZhbGxiYWNrLlxuICAgICAgICBpZiAoIXJlc2V0UGxhY2VtZW50KSB7XG4gICAgICAgICAgc3dpdGNoIChmYWxsYmFja1N0cmF0ZWd5KSB7XG4gICAgICAgICAgICBjYXNlICdiZXN0Rml0JzpcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHZhciBfb3ZlcmZsb3dzRGF0YSRmaWx0ZXIyO1xuICAgICAgICAgICAgICAgIGNvbnN0IHBsYWNlbWVudCA9IChfb3ZlcmZsb3dzRGF0YSRmaWx0ZXIyID0gb3ZlcmZsb3dzRGF0YS5maWx0ZXIoZCA9PiB7XG4gICAgICAgICAgICAgICAgICBpZiAoaGFzRmFsbGJhY2tBeGlzU2lkZURpcmVjdGlvbikge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBjdXJyZW50U2lkZUF4aXMgPSBnZXRTaWRlQXhpcyhkLnBsYWNlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBjdXJyZW50U2lkZUF4aXMgPT09IGluaXRpYWxTaWRlQXhpcyB8fFxuICAgICAgICAgICAgICAgICAgICAvLyBDcmVhdGUgYSBiaWFzIHRvIHRoZSBgeWAgc2lkZSBheGlzIGR1ZSB0byBob3Jpem9udGFsXG4gICAgICAgICAgICAgICAgICAgIC8vIHJlYWRpbmcgZGlyZWN0aW9ucyBmYXZvcmluZyBncmVhdGVyIHdpZHRoLlxuICAgICAgICAgICAgICAgICAgICBjdXJyZW50U2lkZUF4aXMgPT09ICd5JztcbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICAgICAgICAgIH0pLm1hcChkID0+IFtkLnBsYWNlbWVudCwgZC5vdmVyZmxvd3MuZmlsdGVyKG92ZXJmbG93ID0+IG92ZXJmbG93ID4gMCkucmVkdWNlKChhY2MsIG92ZXJmbG93KSA9PiBhY2MgKyBvdmVyZmxvdywgMCldKS5zb3J0KChhLCBiKSA9PiBhWzFdIC0gYlsxXSlbMF0pID09IG51bGwgPyB2b2lkIDAgOiBfb3ZlcmZsb3dzRGF0YSRmaWx0ZXIyWzBdO1xuICAgICAgICAgICAgICAgIGlmIChwbGFjZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgIHJlc2V0UGxhY2VtZW50ID0gcGxhY2VtZW50O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2FzZSAnaW5pdGlhbFBsYWNlbWVudCc6XG4gICAgICAgICAgICAgIHJlc2V0UGxhY2VtZW50ID0gaW5pdGlhbFBsYWNlbWVudDtcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmIChwbGFjZW1lbnQgIT09IHJlc2V0UGxhY2VtZW50KSB7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHJlc2V0OiB7XG4gICAgICAgICAgICAgIHBsYWNlbWVudDogcmVzZXRQbGFjZW1lbnRcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuICB9O1xufTtcblxuZnVuY3Rpb24gZ2V0U2lkZU9mZnNldHMob3ZlcmZsb3csIHJlY3QpIHtcbiAgcmV0dXJuIHtcbiAgICB0b3A6IG92ZXJmbG93LnRvcCAtIHJlY3QuaGVpZ2h0LFxuICAgIHJpZ2h0OiBvdmVyZmxvdy5yaWdodCAtIHJlY3Qud2lkdGgsXG4gICAgYm90dG9tOiBvdmVyZmxvdy5ib3R0b20gLSByZWN0LmhlaWdodCxcbiAgICBsZWZ0OiBvdmVyZmxvdy5sZWZ0IC0gcmVjdC53aWR0aFxuICB9O1xufVxuZnVuY3Rpb24gaXNBbnlTaWRlRnVsbHlDbGlwcGVkKG92ZXJmbG93KSB7XG4gIHJldHVybiBzaWRlcy5zb21lKHNpZGUgPT4gb3ZlcmZsb3dbc2lkZV0gPj0gMCk7XG59XG4vKipcbiAqIFByb3ZpZGVzIGRhdGEgdG8gaGlkZSB0aGUgZmxvYXRpbmcgZWxlbWVudCBpbiBhcHBsaWNhYmxlIHNpdHVhdGlvbnMsIHN1Y2ggYXNcbiAqIHdoZW4gaXQgaXMgbm90IGluIHRoZSBzYW1lIGNsaXBwaW5nIGNvbnRleHQgYXMgdGhlIHJlZmVyZW5jZSBlbGVtZW50LlxuICogQHNlZSBodHRwczovL2Zsb2F0aW5nLXVpLmNvbS9kb2NzL2hpZGVcbiAqL1xuY29uc3QgaGlkZSA9IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gIGlmIChvcHRpb25zID09PSB2b2lkIDApIHtcbiAgICBvcHRpb25zID0ge307XG4gIH1cbiAgcmV0dXJuIHtcbiAgICBuYW1lOiAnaGlkZScsXG4gICAgb3B0aW9ucyxcbiAgICBhc3luYyBmbihzdGF0ZSkge1xuICAgICAgY29uc3Qge1xuICAgICAgICByZWN0c1xuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBzdHJhdGVneSA9ICdyZWZlcmVuY2VIaWRkZW4nLFxuICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnNcbiAgICAgIH0gPSBldmFsdWF0ZShvcHRpb25zLCBzdGF0ZSk7XG4gICAgICBzd2l0Y2ggKHN0cmF0ZWd5KSB7XG4gICAgICAgIGNhc2UgJ3JlZmVyZW5jZUhpZGRlbic6XG4gICAgICAgICAge1xuICAgICAgICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwge1xuICAgICAgICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnMsXG4gICAgICAgICAgICAgIGVsZW1lbnRDb250ZXh0OiAncmVmZXJlbmNlJ1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBjb25zdCBvZmZzZXRzID0gZ2V0U2lkZU9mZnNldHMob3ZlcmZsb3csIHJlY3RzLnJlZmVyZW5jZSk7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAgICAgcmVmZXJlbmNlSGlkZGVuT2Zmc2V0czogb2Zmc2V0cyxcbiAgICAgICAgICAgICAgICByZWZlcmVuY2VIaWRkZW46IGlzQW55U2lkZUZ1bGx5Q2xpcHBlZChvZmZzZXRzKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgY2FzZSAnZXNjYXBlZCc6XG4gICAgICAgICAge1xuICAgICAgICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwge1xuICAgICAgICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnMsXG4gICAgICAgICAgICAgIGFsdEJvdW5kYXJ5OiB0cnVlXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGNvbnN0IG9mZnNldHMgPSBnZXRTaWRlT2Zmc2V0cyhvdmVyZmxvdywgcmVjdHMuZmxvYXRpbmcpO1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgZGF0YToge1xuICAgICAgICAgICAgICAgIGVzY2FwZWRPZmZzZXRzOiBvZmZzZXRzLFxuICAgICAgICAgICAgICAgIGVzY2FwZWQ6IGlzQW55U2lkZUZ1bGx5Q2xpcHBlZChvZmZzZXRzKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICB7XG4gICAgICAgICAgICByZXR1cm4ge307XG4gICAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfTtcbn07XG5cbmZ1bmN0aW9uIGdldEJvdW5kaW5nUmVjdChyZWN0cykge1xuICBjb25zdCBtaW5YID0gbWluKC4uLnJlY3RzLm1hcChyZWN0ID0+IHJlY3QubGVmdCkpO1xuICBjb25zdCBtaW5ZID0gbWluKC4uLnJlY3RzLm1hcChyZWN0ID0+IHJlY3QudG9wKSk7XG4gIGNvbnN0IG1heFggPSBtYXgoLi4ucmVjdHMubWFwKHJlY3QgPT4gcmVjdC5yaWdodCkpO1xuICBjb25zdCBtYXhZID0gbWF4KC4uLnJlY3RzLm1hcChyZWN0ID0+IHJlY3QuYm90dG9tKSk7XG4gIHJldHVybiB7XG4gICAgeDogbWluWCxcbiAgICB5OiBtaW5ZLFxuICAgIHdpZHRoOiBtYXhYIC0gbWluWCxcbiAgICBoZWlnaHQ6IG1heFkgLSBtaW5ZXG4gIH07XG59XG5mdW5jdGlvbiBnZXRSZWN0c0J5TGluZShyZWN0cykge1xuICBjb25zdCBzb3J0ZWRSZWN0cyA9IHJlY3RzLnNsaWNlKCkuc29ydCgoYSwgYikgPT4gYS55IC0gYi55KTtcbiAgY29uc3QgZ3JvdXBzID0gW107XG4gIGxldCBwcmV2UmVjdCA9IG51bGw7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgc29ydGVkUmVjdHMubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCByZWN0ID0gc29ydGVkUmVjdHNbaV07XG4gICAgaWYgKCFwcmV2UmVjdCB8fCByZWN0LnkgLSBwcmV2UmVjdC55ID4gcHJldlJlY3QuaGVpZ2h0IC8gMikge1xuICAgICAgZ3JvdXBzLnB1c2goW3JlY3RdKTtcbiAgICB9IGVsc2Uge1xuICAgICAgZ3JvdXBzW2dyb3Vwcy5sZW5ndGggLSAxXS5wdXNoKHJlY3QpO1xuICAgIH1cbiAgICBwcmV2UmVjdCA9IHJlY3Q7XG4gIH1cbiAgcmV0dXJuIGdyb3Vwcy5tYXAocmVjdCA9PiByZWN0VG9DbGllbnRSZWN0KGdldEJvdW5kaW5nUmVjdChyZWN0KSkpO1xufVxuLyoqXG4gKiBQcm92aWRlcyBpbXByb3ZlZCBwb3NpdGlvbmluZyBmb3IgaW5saW5lIHJlZmVyZW5jZSBlbGVtZW50cyB0aGF0IGNhbiBzcGFuXG4gKiBvdmVyIG11bHRpcGxlIGxpbmVzLCBzdWNoIGFzIGh5cGVybGlua3Mgb3IgcmFuZ2Ugc2VsZWN0aW9ucy5cbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9pbmxpbmVcbiAqL1xuY29uc3QgaW5saW5lID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG5hbWU6ICdpbmxpbmUnLFxuICAgIG9wdGlvbnMsXG4gICAgYXN5bmMgZm4oc3RhdGUpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcGxhY2VtZW50LFxuICAgICAgICBlbGVtZW50cyxcbiAgICAgICAgcmVjdHMsXG4gICAgICAgIHBsYXRmb3JtLFxuICAgICAgICBzdHJhdGVneVxuICAgICAgfSA9IHN0YXRlO1xuICAgICAgLy8gQSBNb3VzZUV2ZW50J3MgY2xpZW50e1gsWX0gY29vcmRzIGNhbiBiZSB1cCB0byAyIHBpeGVscyBvZmYgYVxuICAgICAgLy8gQ2xpZW50UmVjdCdzIGJvdW5kcywgZGVzcGl0ZSB0aGUgZXZlbnQgbGlzdGVuZXIgYmVpbmcgdHJpZ2dlcmVkLiBBXG4gICAgICAvLyBwYWRkaW5nIG9mIDIgc2VlbXMgdG8gaGFuZGxlIHRoaXMgaXNzdWUuXG4gICAgICBjb25zdCB7XG4gICAgICAgIHBhZGRpbmcgPSAyLFxuICAgICAgICB4LFxuICAgICAgICB5XG4gICAgICB9ID0gZXZhbHVhdGUob3B0aW9ucywgc3RhdGUpO1xuICAgICAgY29uc3QgbmF0aXZlQ2xpZW50UmVjdHMgPSBBcnJheS5mcm9tKChhd2FpdCAocGxhdGZvcm0uZ2V0Q2xpZW50UmVjdHMgPT0gbnVsbCA/IHZvaWQgMCA6IHBsYXRmb3JtLmdldENsaWVudFJlY3RzKGVsZW1lbnRzLnJlZmVyZW5jZSkpKSB8fCBbXSk7XG4gICAgICBjb25zdCBjbGllbnRSZWN0cyA9IGdldFJlY3RzQnlMaW5lKG5hdGl2ZUNsaWVudFJlY3RzKTtcbiAgICAgIGNvbnN0IGZhbGxiYWNrID0gcmVjdFRvQ2xpZW50UmVjdChnZXRCb3VuZGluZ1JlY3QobmF0aXZlQ2xpZW50UmVjdHMpKTtcbiAgICAgIGNvbnN0IHBhZGRpbmdPYmplY3QgPSBnZXRQYWRkaW5nT2JqZWN0KHBhZGRpbmcpO1xuICAgICAgZnVuY3Rpb24gZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkge1xuICAgICAgICAvLyBUaGVyZSBhcmUgdHdvIHJlY3RzIGFuZCB0aGV5IGFyZSBkaXNqb2luZWQuXG4gICAgICAgIGlmIChjbGllbnRSZWN0cy5sZW5ndGggPT09IDIgJiYgY2xpZW50UmVjdHNbMF0ubGVmdCA+IGNsaWVudFJlY3RzWzFdLnJpZ2h0ICYmIHggIT0gbnVsbCAmJiB5ICE9IG51bGwpIHtcbiAgICAgICAgICAvLyBGaW5kIHRoZSBmaXJzdCByZWN0IGluIHdoaWNoIHRoZSBwb2ludCBpcyBmdWxseSBpbnNpZGUuXG4gICAgICAgICAgcmV0dXJuIGNsaWVudFJlY3RzLmZpbmQocmVjdCA9PiB4ID4gcmVjdC5sZWZ0IC0gcGFkZGluZ09iamVjdC5sZWZ0ICYmIHggPCByZWN0LnJpZ2h0ICsgcGFkZGluZ09iamVjdC5yaWdodCAmJiB5ID4gcmVjdC50b3AgLSBwYWRkaW5nT2JqZWN0LnRvcCAmJiB5IDwgcmVjdC5ib3R0b20gKyBwYWRkaW5nT2JqZWN0LmJvdHRvbSkgfHwgZmFsbGJhY2s7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUaGVyZSBhcmUgMiBvciBtb3JlIGNvbm5lY3RlZCByZWN0cy5cbiAgICAgICAgaWYgKGNsaWVudFJlY3RzLmxlbmd0aCA+PSAyKSB7XG4gICAgICAgICAgaWYgKGdldFNpZGVBeGlzKHBsYWNlbWVudCkgPT09ICd5Jykge1xuICAgICAgICAgICAgY29uc3QgZmlyc3RSZWN0ID0gY2xpZW50UmVjdHNbMF07XG4gICAgICAgICAgICBjb25zdCBsYXN0UmVjdCA9IGNsaWVudFJlY3RzW2NsaWVudFJlY3RzLmxlbmd0aCAtIDFdO1xuICAgICAgICAgICAgY29uc3QgaXNUb3AgPSBnZXRTaWRlKHBsYWNlbWVudCkgPT09ICd0b3AnO1xuICAgICAgICAgICAgY29uc3QgdG9wID0gZmlyc3RSZWN0LnRvcDtcbiAgICAgICAgICAgIGNvbnN0IGJvdHRvbSA9IGxhc3RSZWN0LmJvdHRvbTtcbiAgICAgICAgICAgIGNvbnN0IGxlZnQgPSBpc1RvcCA/IGZpcnN0UmVjdC5sZWZ0IDogbGFzdFJlY3QubGVmdDtcbiAgICAgICAgICAgIGNvbnN0IHJpZ2h0ID0gaXNUb3AgPyBmaXJzdFJlY3QucmlnaHQgOiBsYXN0UmVjdC5yaWdodDtcbiAgICAgICAgICAgIGNvbnN0IHdpZHRoID0gcmlnaHQgLSBsZWZ0O1xuICAgICAgICAgICAgY29uc3QgaGVpZ2h0ID0gYm90dG9tIC0gdG9wO1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgdG9wLFxuICAgICAgICAgICAgICBib3R0b20sXG4gICAgICAgICAgICAgIGxlZnQsXG4gICAgICAgICAgICAgIHJpZ2h0LFxuICAgICAgICAgICAgICB3aWR0aCxcbiAgICAgICAgICAgICAgaGVpZ2h0LFxuICAgICAgICAgICAgICB4OiBsZWZ0LFxuICAgICAgICAgICAgICB5OiB0b3BcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IGlzTGVmdFNpZGUgPSBnZXRTaWRlKHBsYWNlbWVudCkgPT09ICdsZWZ0JztcbiAgICAgICAgICBjb25zdCBtYXhSaWdodCA9IG1heCguLi5jbGllbnRSZWN0cy5tYXAocmVjdCA9PiByZWN0LnJpZ2h0KSk7XG4gICAgICAgICAgY29uc3QgbWluTGVmdCA9IG1pbiguLi5jbGllbnRSZWN0cy5tYXAocmVjdCA9PiByZWN0LmxlZnQpKTtcbiAgICAgICAgICBjb25zdCBtZWFzdXJlUmVjdHMgPSBjbGllbnRSZWN0cy5maWx0ZXIocmVjdCA9PiBpc0xlZnRTaWRlID8gcmVjdC5sZWZ0ID09PSBtaW5MZWZ0IDogcmVjdC5yaWdodCA9PT0gbWF4UmlnaHQpO1xuICAgICAgICAgIGNvbnN0IHRvcCA9IG1lYXN1cmVSZWN0c1swXS50b3A7XG4gICAgICAgICAgY29uc3QgYm90dG9tID0gbWVhc3VyZVJlY3RzW21lYXN1cmVSZWN0cy5sZW5ndGggLSAxXS5ib3R0b207XG4gICAgICAgICAgY29uc3QgbGVmdCA9IG1pbkxlZnQ7XG4gICAgICAgICAgY29uc3QgcmlnaHQgPSBtYXhSaWdodDtcbiAgICAgICAgICBjb25zdCB3aWR0aCA9IHJpZ2h0IC0gbGVmdDtcbiAgICAgICAgICBjb25zdCBoZWlnaHQgPSBib3R0b20gLSB0b3A7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHRvcCxcbiAgICAgICAgICAgIGJvdHRvbSxcbiAgICAgICAgICAgIGxlZnQsXG4gICAgICAgICAgICByaWdodCxcbiAgICAgICAgICAgIHdpZHRoLFxuICAgICAgICAgICAgaGVpZ2h0LFxuICAgICAgICAgICAgeDogbGVmdCxcbiAgICAgICAgICAgIHk6IHRvcFxuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGZhbGxiYWNrO1xuICAgICAgfVxuICAgICAgY29uc3QgcmVzZXRSZWN0cyA9IGF3YWl0IHBsYXRmb3JtLmdldEVsZW1lbnRSZWN0cyh7XG4gICAgICAgIHJlZmVyZW5jZToge1xuICAgICAgICAgIGdldEJvdW5kaW5nQ2xpZW50UmVjdFxuICAgICAgICB9LFxuICAgICAgICBmbG9hdGluZzogZWxlbWVudHMuZmxvYXRpbmcsXG4gICAgICAgIHN0cmF0ZWd5XG4gICAgICB9KTtcbiAgICAgIGlmIChyZWN0cy5yZWZlcmVuY2UueCAhPT0gcmVzZXRSZWN0cy5yZWZlcmVuY2UueCB8fCByZWN0cy5yZWZlcmVuY2UueSAhPT0gcmVzZXRSZWN0cy5yZWZlcmVuY2UueSB8fCByZWN0cy5yZWZlcmVuY2Uud2lkdGggIT09IHJlc2V0UmVjdHMucmVmZXJlbmNlLndpZHRoIHx8IHJlY3RzLnJlZmVyZW5jZS5oZWlnaHQgIT09IHJlc2V0UmVjdHMucmVmZXJlbmNlLmhlaWdodCkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHJlc2V0OiB7XG4gICAgICAgICAgICByZWN0czogcmVzZXRSZWN0c1xuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB7fTtcbiAgICB9XG4gIH07XG59O1xuXG5jb25zdCBvcmlnaW5TaWRlcyA9IC8qI19fUFVSRV9fKi9uZXcgU2V0KFsnbGVmdCcsICd0b3AnXSk7XG5cbi8vIEZvciB0eXBlIGJhY2t3YXJkcy1jb21wYXRpYmlsaXR5LCB0aGUgYE9mZnNldE9wdGlvbnNgIHR5cGUgd2FzIGFsc29cbi8vIERlcml2YWJsZS5cblxuYXN5bmMgZnVuY3Rpb24gY29udmVydFZhbHVlVG9Db29yZHMoc3RhdGUsIG9wdGlvbnMpIHtcbiAgY29uc3Qge1xuICAgIHBsYWNlbWVudCxcbiAgICBwbGF0Zm9ybSxcbiAgICBlbGVtZW50c1xuICB9ID0gc3RhdGU7XG4gIGNvbnN0IHJ0bCA9IGF3YWl0IChwbGF0Zm9ybS5pc1JUTCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNSVEwoZWxlbWVudHMuZmxvYXRpbmcpKTtcbiAgY29uc3Qgc2lkZSA9IGdldFNpZGUocGxhY2VtZW50KTtcbiAgY29uc3QgYWxpZ25tZW50ID0gZ2V0QWxpZ25tZW50KHBsYWNlbWVudCk7XG4gIGNvbnN0IGlzVmVydGljYWwgPSBnZXRTaWRlQXhpcyhwbGFjZW1lbnQpID09PSAneSc7XG4gIGNvbnN0IG1haW5BeGlzTXVsdGkgPSBvcmlnaW5TaWRlcy5oYXMoc2lkZSkgPyAtMSA6IDE7XG4gIGNvbnN0IGNyb3NzQXhpc011bHRpID0gcnRsICYmIGlzVmVydGljYWwgPyAtMSA6IDE7XG4gIGNvbnN0IHJhd1ZhbHVlID0gZXZhbHVhdGUob3B0aW9ucywgc3RhdGUpO1xuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBwcmVmZXItY29uc3RcbiAgbGV0IHtcbiAgICBtYWluQXhpcyxcbiAgICBjcm9zc0F4aXMsXG4gICAgYWxpZ25tZW50QXhpc1xuICB9ID0gdHlwZW9mIHJhd1ZhbHVlID09PSAnbnVtYmVyJyA/IHtcbiAgICBtYWluQXhpczogcmF3VmFsdWUsXG4gICAgY3Jvc3NBeGlzOiAwLFxuICAgIGFsaWdubWVudEF4aXM6IG51bGxcbiAgfSA6IHtcbiAgICBtYWluQXhpczogcmF3VmFsdWUubWFpbkF4aXMgfHwgMCxcbiAgICBjcm9zc0F4aXM6IHJhd1ZhbHVlLmNyb3NzQXhpcyB8fCAwLFxuICAgIGFsaWdubWVudEF4aXM6IHJhd1ZhbHVlLmFsaWdubWVudEF4aXNcbiAgfTtcbiAgaWYgKGFsaWdubWVudCAmJiB0eXBlb2YgYWxpZ25tZW50QXhpcyA9PT0gJ251bWJlcicpIHtcbiAgICBjcm9zc0F4aXMgPSBhbGlnbm1lbnQgPT09ICdlbmQnID8gYWxpZ25tZW50QXhpcyAqIC0xIDogYWxpZ25tZW50QXhpcztcbiAgfVxuICByZXR1cm4gaXNWZXJ0aWNhbCA/IHtcbiAgICB4OiBjcm9zc0F4aXMgKiBjcm9zc0F4aXNNdWx0aSxcbiAgICB5OiBtYWluQXhpcyAqIG1haW5BeGlzTXVsdGlcbiAgfSA6IHtcbiAgICB4OiBtYWluQXhpcyAqIG1haW5BeGlzTXVsdGksXG4gICAgeTogY3Jvc3NBeGlzICogY3Jvc3NBeGlzTXVsdGlcbiAgfTtcbn1cblxuLyoqXG4gKiBNb2RpZmllcyB0aGUgcGxhY2VtZW50IGJ5IHRyYW5zbGF0aW5nIHRoZSBmbG9hdGluZyBlbGVtZW50IGFsb25nIHRoZVxuICogc3BlY2lmaWVkIGF4ZXMuXG4gKiBBIG51bWJlciAoc2hvcnRoYW5kIGZvciBgbWFpbkF4aXNgIG9yIGRpc3RhbmNlKSwgb3IgYW4gYXhlcyBjb25maWd1cmF0aW9uXG4gKiBvYmplY3QgbWF5IGJlIHBhc3NlZC5cbiAqIEBzZWUgaHR0cHM6Ly9mbG9hdGluZy11aS5jb20vZG9jcy9vZmZzZXRcbiAqL1xuY29uc3Qgb2Zmc2V0ID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSAwO1xuICB9XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ29mZnNldCcsXG4gICAgb3B0aW9ucyxcbiAgICBhc3luYyBmbihzdGF0ZSkge1xuICAgICAgdmFyIF9taWRkbGV3YXJlRGF0YSRvZmZzZSwgX21pZGRsZXdhcmVEYXRhJGFycm93O1xuICAgICAgY29uc3Qge1xuICAgICAgICB4LFxuICAgICAgICB5LFxuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIG1pZGRsZXdhcmVEYXRhXG4gICAgICB9ID0gc3RhdGU7XG4gICAgICBjb25zdCBkaWZmQ29vcmRzID0gYXdhaXQgY29udmVydFZhbHVlVG9Db29yZHMoc3RhdGUsIG9wdGlvbnMpO1xuXG4gICAgICAvLyBJZiB0aGUgcGxhY2VtZW50IGlzIHRoZSBzYW1lIGFuZCB0aGUgYXJyb3cgY2F1c2VkIGFuIGFsaWdubWVudCBvZmZzZXRcbiAgICAgIC8vIHRoZW4gd2UgZG9uJ3QgbmVlZCB0byBjaGFuZ2UgdGhlIHBvc2l0aW9uaW5nIGNvb3JkaW5hdGVzLlxuICAgICAgaWYgKHBsYWNlbWVudCA9PT0gKChfbWlkZGxld2FyZURhdGEkb2Zmc2UgPSBtaWRkbGV3YXJlRGF0YS5vZmZzZXQpID09IG51bGwgPyB2b2lkIDAgOiBfbWlkZGxld2FyZURhdGEkb2Zmc2UucGxhY2VtZW50KSAmJiAoX21pZGRsZXdhcmVEYXRhJGFycm93ID0gbWlkZGxld2FyZURhdGEuYXJyb3cpICE9IG51bGwgJiYgX21pZGRsZXdhcmVEYXRhJGFycm93LmFsaWdubWVudE9mZnNldCkge1xuICAgICAgICByZXR1cm4ge307XG4gICAgICB9XG4gICAgICByZXR1cm4ge1xuICAgICAgICB4OiB4ICsgZGlmZkNvb3Jkcy54LFxuICAgICAgICB5OiB5ICsgZGlmZkNvb3Jkcy55LFxuICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgLi4uZGlmZkNvb3JkcyxcbiAgICAgICAgICBwbGFjZW1lbnRcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICB9XG4gIH07XG59O1xuXG4vKipcbiAqIE9wdGltaXplcyB0aGUgdmlzaWJpbGl0eSBvZiB0aGUgZmxvYXRpbmcgZWxlbWVudCBieSBzaGlmdGluZyBpdCBpbiBvcmRlciB0b1xuICoga2VlcCBpdCBpbiB2aWV3IHdoZW4gaXQgd2lsbCBvdmVyZmxvdyB0aGUgY2xpcHBpbmcgYm91bmRhcnkuXG4gKiBAc2VlIGh0dHBzOi8vZmxvYXRpbmctdWkuY29tL2RvY3Mvc2hpZnRcbiAqL1xuY29uc3Qgc2hpZnQgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICBpZiAob3B0aW9ucyA9PT0gdm9pZCAwKSB7XG4gICAgb3B0aW9ucyA9IHt9O1xuICB9XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ3NoaWZ0JyxcbiAgICBvcHRpb25zLFxuICAgIGFzeW5jIGZuKHN0YXRlKSB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIHgsXG4gICAgICAgIHksXG4gICAgICAgIHBsYWNlbWVudFxuICAgICAgfSA9IHN0YXRlO1xuICAgICAgY29uc3Qge1xuICAgICAgICBtYWluQXhpczogY2hlY2tNYWluQXhpcyA9IHRydWUsXG4gICAgICAgIGNyb3NzQXhpczogY2hlY2tDcm9zc0F4aXMgPSBmYWxzZSxcbiAgICAgICAgbGltaXRlciA9IHtcbiAgICAgICAgICBmbjogX3JlZiA9PiB7XG4gICAgICAgICAgICBsZXQge1xuICAgICAgICAgICAgICB4LFxuICAgICAgICAgICAgICB5XG4gICAgICAgICAgICB9ID0gX3JlZjtcbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgIHgsXG4gICAgICAgICAgICAgIHlcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICAuLi5kZXRlY3RPdmVyZmxvd09wdGlvbnNcbiAgICAgIH0gPSBldmFsdWF0ZShvcHRpb25zLCBzdGF0ZSk7XG4gICAgICBjb25zdCBjb29yZHMgPSB7XG4gICAgICAgIHgsXG4gICAgICAgIHlcbiAgICAgIH07XG4gICAgICBjb25zdCBvdmVyZmxvdyA9IGF3YWl0IGRldGVjdE92ZXJmbG93KHN0YXRlLCBkZXRlY3RPdmVyZmxvd09wdGlvbnMpO1xuICAgICAgY29uc3QgY3Jvc3NBeGlzID0gZ2V0U2lkZUF4aXMoZ2V0U2lkZShwbGFjZW1lbnQpKTtcbiAgICAgIGNvbnN0IG1haW5BeGlzID0gZ2V0T3Bwb3NpdGVBeGlzKGNyb3NzQXhpcyk7XG4gICAgICBsZXQgbWFpbkF4aXNDb29yZCA9IGNvb3Jkc1ttYWluQXhpc107XG4gICAgICBsZXQgY3Jvc3NBeGlzQ29vcmQgPSBjb29yZHNbY3Jvc3NBeGlzXTtcbiAgICAgIGlmIChjaGVja01haW5BeGlzKSB7XG4gICAgICAgIGNvbnN0IG1pblNpZGUgPSBtYWluQXhpcyA9PT0gJ3knID8gJ3RvcCcgOiAnbGVmdCc7XG4gICAgICAgIGNvbnN0IG1heFNpZGUgPSBtYWluQXhpcyA9PT0gJ3knID8gJ2JvdHRvbScgOiAncmlnaHQnO1xuICAgICAgICBjb25zdCBtaW4gPSBtYWluQXhpc0Nvb3JkICsgb3ZlcmZsb3dbbWluU2lkZV07XG4gICAgICAgIGNvbnN0IG1heCA9IG1haW5BeGlzQ29vcmQgLSBvdmVyZmxvd1ttYXhTaWRlXTtcbiAgICAgICAgbWFpbkF4aXNDb29yZCA9IGNsYW1wKG1pbiwgbWFpbkF4aXNDb29yZCwgbWF4KTtcbiAgICAgIH1cbiAgICAgIGlmIChjaGVja0Nyb3NzQXhpcykge1xuICAgICAgICBjb25zdCBtaW5TaWRlID0gY3Jvc3NBeGlzID09PSAneScgPyAndG9wJyA6ICdsZWZ0JztcbiAgICAgICAgY29uc3QgbWF4U2lkZSA9IGNyb3NzQXhpcyA9PT0gJ3knID8gJ2JvdHRvbScgOiAncmlnaHQnO1xuICAgICAgICBjb25zdCBtaW4gPSBjcm9zc0F4aXNDb29yZCArIG92ZXJmbG93W21pblNpZGVdO1xuICAgICAgICBjb25zdCBtYXggPSBjcm9zc0F4aXNDb29yZCAtIG92ZXJmbG93W21heFNpZGVdO1xuICAgICAgICBjcm9zc0F4aXNDb29yZCA9IGNsYW1wKG1pbiwgY3Jvc3NBeGlzQ29vcmQsIG1heCk7XG4gICAgICB9XG4gICAgICBjb25zdCBsaW1pdGVkQ29vcmRzID0gbGltaXRlci5mbih7XG4gICAgICAgIC4uLnN0YXRlLFxuICAgICAgICBbbWFpbkF4aXNdOiBtYWluQXhpc0Nvb3JkLFxuICAgICAgICBbY3Jvc3NBeGlzXTogY3Jvc3NBeGlzQ29vcmRcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgLi4ubGltaXRlZENvb3JkcyxcbiAgICAgICAgZGF0YToge1xuICAgICAgICAgIHg6IGxpbWl0ZWRDb29yZHMueCAtIHgsXG4gICAgICAgICAgeTogbGltaXRlZENvb3Jkcy55IC0geSxcbiAgICAgICAgICBlbmFibGVkOiB7XG4gICAgICAgICAgICBbbWFpbkF4aXNdOiBjaGVja01haW5BeGlzLFxuICAgICAgICAgICAgW2Nyb3NzQXhpc106IGNoZWNrQ3Jvc3NBeGlzXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9O1xuICAgIH1cbiAgfTtcbn07XG4vKipcbiAqIEJ1aWx0LWluIGBsaW1pdGVyYCB0aGF0IHdpbGwgc3RvcCBgc2hpZnQoKWAgYXQgYSBjZXJ0YWluIHBvaW50LlxuICovXG5jb25zdCBsaW1pdFNoaWZ0ID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG9wdGlvbnMsXG4gICAgZm4oc3RhdGUpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgeCxcbiAgICAgICAgeSxcbiAgICAgICAgcGxhY2VtZW50LFxuICAgICAgICByZWN0cyxcbiAgICAgICAgbWlkZGxld2FyZURhdGFcbiAgICAgIH0gPSBzdGF0ZTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgb2Zmc2V0ID0gMCxcbiAgICAgICAgbWFpbkF4aXM6IGNoZWNrTWFpbkF4aXMgPSB0cnVlLFxuICAgICAgICBjcm9zc0F4aXM6IGNoZWNrQ3Jvc3NBeGlzID0gdHJ1ZVxuICAgICAgfSA9IGV2YWx1YXRlKG9wdGlvbnMsIHN0YXRlKTtcbiAgICAgIGNvbnN0IGNvb3JkcyA9IHtcbiAgICAgICAgeCxcbiAgICAgICAgeVxuICAgICAgfTtcbiAgICAgIGNvbnN0IGNyb3NzQXhpcyA9IGdldFNpZGVBeGlzKHBsYWNlbWVudCk7XG4gICAgICBjb25zdCBtYWluQXhpcyA9IGdldE9wcG9zaXRlQXhpcyhjcm9zc0F4aXMpO1xuICAgICAgbGV0IG1haW5BeGlzQ29vcmQgPSBjb29yZHNbbWFpbkF4aXNdO1xuICAgICAgbGV0IGNyb3NzQXhpc0Nvb3JkID0gY29vcmRzW2Nyb3NzQXhpc107XG4gICAgICBjb25zdCByYXdPZmZzZXQgPSBldmFsdWF0ZShvZmZzZXQsIHN0YXRlKTtcbiAgICAgIGNvbnN0IGNvbXB1dGVkT2Zmc2V0ID0gdHlwZW9mIHJhd09mZnNldCA9PT0gJ251bWJlcicgPyB7XG4gICAgICAgIG1haW5BeGlzOiByYXdPZmZzZXQsXG4gICAgICAgIGNyb3NzQXhpczogMFxuICAgICAgfSA6IHtcbiAgICAgICAgbWFpbkF4aXM6IDAsXG4gICAgICAgIGNyb3NzQXhpczogMCxcbiAgICAgICAgLi4ucmF3T2Zmc2V0XG4gICAgICB9O1xuICAgICAgaWYgKGNoZWNrTWFpbkF4aXMpIHtcbiAgICAgICAgY29uc3QgbGVuID0gbWFpbkF4aXMgPT09ICd5JyA/ICdoZWlnaHQnIDogJ3dpZHRoJztcbiAgICAgICAgY29uc3QgbGltaXRNaW4gPSByZWN0cy5yZWZlcmVuY2VbbWFpbkF4aXNdIC0gcmVjdHMuZmxvYXRpbmdbbGVuXSArIGNvbXB1dGVkT2Zmc2V0Lm1haW5BeGlzO1xuICAgICAgICBjb25zdCBsaW1pdE1heCA9IHJlY3RzLnJlZmVyZW5jZVttYWluQXhpc10gKyByZWN0cy5yZWZlcmVuY2VbbGVuXSAtIGNvbXB1dGVkT2Zmc2V0Lm1haW5BeGlzO1xuICAgICAgICBpZiAobWFpbkF4aXNDb29yZCA8IGxpbWl0TWluKSB7XG4gICAgICAgICAgbWFpbkF4aXNDb29yZCA9IGxpbWl0TWluO1xuICAgICAgICB9IGVsc2UgaWYgKG1haW5BeGlzQ29vcmQgPiBsaW1pdE1heCkge1xuICAgICAgICAgIG1haW5BeGlzQ29vcmQgPSBsaW1pdE1heDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKGNoZWNrQ3Jvc3NBeGlzKSB7XG4gICAgICAgIHZhciBfbWlkZGxld2FyZURhdGEkb2Zmc2UsIF9taWRkbGV3YXJlRGF0YSRvZmZzZTI7XG4gICAgICAgIGNvbnN0IGxlbiA9IG1haW5BeGlzID09PSAneScgPyAnd2lkdGgnIDogJ2hlaWdodCc7XG4gICAgICAgIGNvbnN0IGlzT3JpZ2luU2lkZSA9IG9yaWdpblNpZGVzLmhhcyhnZXRTaWRlKHBsYWNlbWVudCkpO1xuICAgICAgICBjb25zdCBsaW1pdE1pbiA9IHJlY3RzLnJlZmVyZW5jZVtjcm9zc0F4aXNdIC0gcmVjdHMuZmxvYXRpbmdbbGVuXSArIChpc09yaWdpblNpZGUgPyAoKF9taWRkbGV3YXJlRGF0YSRvZmZzZSA9IG1pZGRsZXdhcmVEYXRhLm9mZnNldCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9taWRkbGV3YXJlRGF0YSRvZmZzZVtjcm9zc0F4aXNdKSB8fCAwIDogMCkgKyAoaXNPcmlnaW5TaWRlID8gMCA6IGNvbXB1dGVkT2Zmc2V0LmNyb3NzQXhpcyk7XG4gICAgICAgIGNvbnN0IGxpbWl0TWF4ID0gcmVjdHMucmVmZXJlbmNlW2Nyb3NzQXhpc10gKyByZWN0cy5yZWZlcmVuY2VbbGVuXSArIChpc09yaWdpblNpZGUgPyAwIDogKChfbWlkZGxld2FyZURhdGEkb2Zmc2UyID0gbWlkZGxld2FyZURhdGEub2Zmc2V0KSA9PSBudWxsID8gdm9pZCAwIDogX21pZGRsZXdhcmVEYXRhJG9mZnNlMltjcm9zc0F4aXNdKSB8fCAwKSAtIChpc09yaWdpblNpZGUgPyBjb21wdXRlZE9mZnNldC5jcm9zc0F4aXMgOiAwKTtcbiAgICAgICAgaWYgKGNyb3NzQXhpc0Nvb3JkIDwgbGltaXRNaW4pIHtcbiAgICAgICAgICBjcm9zc0F4aXNDb29yZCA9IGxpbWl0TWluO1xuICAgICAgICB9IGVsc2UgaWYgKGNyb3NzQXhpc0Nvb3JkID4gbGltaXRNYXgpIHtcbiAgICAgICAgICBjcm9zc0F4aXNDb29yZCA9IGxpbWl0TWF4O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4ge1xuICAgICAgICBbbWFpbkF4aXNdOiBtYWluQXhpc0Nvb3JkLFxuICAgICAgICBbY3Jvc3NBeGlzXTogY3Jvc3NBeGlzQ29vcmRcbiAgICAgIH07XG4gICAgfVxuICB9O1xufTtcblxuLyoqXG4gKiBQcm92aWRlcyBkYXRhIHRoYXQgYWxsb3dzIHlvdSB0byBjaGFuZ2UgdGhlIHNpemUgb2YgdGhlIGZsb2F0aW5nIGVsZW1lbnQg4oCUXG4gKiBmb3IgaW5zdGFuY2UsIHByZXZlbnQgaXQgZnJvbSBvdmVyZmxvd2luZyB0aGUgY2xpcHBpbmcgYm91bmRhcnkgb3IgbWF0Y2ggdGhlXG4gKiB3aWR0aCBvZiB0aGUgcmVmZXJlbmNlIGVsZW1lbnQuXG4gKiBAc2VlIGh0dHBzOi8vZmxvYXRpbmctdWkuY29tL2RvY3Mvc2l6ZVxuICovXG5jb25zdCBzaXplID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuICByZXR1cm4ge1xuICAgIG5hbWU6ICdzaXplJyxcbiAgICBvcHRpb25zLFxuICAgIGFzeW5jIGZuKHN0YXRlKSB7XG4gICAgICB2YXIgX3N0YXRlJG1pZGRsZXdhcmVEYXRhLCBfc3RhdGUkbWlkZGxld2FyZURhdGEyO1xuICAgICAgY29uc3Qge1xuICAgICAgICBwbGFjZW1lbnQsXG4gICAgICAgIHJlY3RzLFxuICAgICAgICBwbGF0Zm9ybSxcbiAgICAgICAgZWxlbWVudHNcbiAgICAgIH0gPSBzdGF0ZTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgYXBwbHkgPSAoKSA9PiB7fSxcbiAgICAgICAgLi4uZGV0ZWN0T3ZlcmZsb3dPcHRpb25zXG4gICAgICB9ID0gZXZhbHVhdGUob3B0aW9ucywgc3RhdGUpO1xuICAgICAgY29uc3Qgb3ZlcmZsb3cgPSBhd2FpdCBkZXRlY3RPdmVyZmxvdyhzdGF0ZSwgZGV0ZWN0T3ZlcmZsb3dPcHRpb25zKTtcbiAgICAgIGNvbnN0IHNpZGUgPSBnZXRTaWRlKHBsYWNlbWVudCk7XG4gICAgICBjb25zdCBhbGlnbm1lbnQgPSBnZXRBbGlnbm1lbnQocGxhY2VtZW50KTtcbiAgICAgIGNvbnN0IGlzWUF4aXMgPSBnZXRTaWRlQXhpcyhwbGFjZW1lbnQpID09PSAneSc7XG4gICAgICBjb25zdCB7XG4gICAgICAgIHdpZHRoLFxuICAgICAgICBoZWlnaHRcbiAgICAgIH0gPSByZWN0cy5mbG9hdGluZztcbiAgICAgIGxldCBoZWlnaHRTaWRlO1xuICAgICAgbGV0IHdpZHRoU2lkZTtcbiAgICAgIGlmIChzaWRlID09PSAndG9wJyB8fCBzaWRlID09PSAnYm90dG9tJykge1xuICAgICAgICBoZWlnaHRTaWRlID0gc2lkZTtcbiAgICAgICAgd2lkdGhTaWRlID0gYWxpZ25tZW50ID09PSAoKGF3YWl0IChwbGF0Zm9ybS5pc1JUTCA9PSBudWxsID8gdm9pZCAwIDogcGxhdGZvcm0uaXNSVEwoZWxlbWVudHMuZmxvYXRpbmcpKSkgPyAnc3RhcnQnIDogJ2VuZCcpID8gJ2xlZnQnIDogJ3JpZ2h0JztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHdpZHRoU2lkZSA9IHNpZGU7XG4gICAgICAgIGhlaWdodFNpZGUgPSBhbGlnbm1lbnQgPT09ICdlbmQnID8gJ3RvcCcgOiAnYm90dG9tJztcbiAgICAgIH1cbiAgICAgIGNvbnN0IG1heGltdW1DbGlwcGluZ0hlaWdodCA9IGhlaWdodCAtIG92ZXJmbG93LnRvcCAtIG92ZXJmbG93LmJvdHRvbTtcbiAgICAgIGNvbnN0IG1heGltdW1DbGlwcGluZ1dpZHRoID0gd2lkdGggLSBvdmVyZmxvdy5sZWZ0IC0gb3ZlcmZsb3cucmlnaHQ7XG4gICAgICBjb25zdCBvdmVyZmxvd0F2YWlsYWJsZUhlaWdodCA9IG1pbihoZWlnaHQgLSBvdmVyZmxvd1toZWlnaHRTaWRlXSwgbWF4aW11bUNsaXBwaW5nSGVpZ2h0KTtcbiAgICAgIGNvbnN0IG92ZXJmbG93QXZhaWxhYmxlV2lkdGggPSBtaW4od2lkdGggLSBvdmVyZmxvd1t3aWR0aFNpZGVdLCBtYXhpbXVtQ2xpcHBpbmdXaWR0aCk7XG4gICAgICBjb25zdCBub1NoaWZ0ID0gIXN0YXRlLm1pZGRsZXdhcmVEYXRhLnNoaWZ0O1xuICAgICAgbGV0IGF2YWlsYWJsZUhlaWdodCA9IG92ZXJmbG93QXZhaWxhYmxlSGVpZ2h0O1xuICAgICAgbGV0IGF2YWlsYWJsZVdpZHRoID0gb3ZlcmZsb3dBdmFpbGFibGVXaWR0aDtcbiAgICAgIGlmICgoX3N0YXRlJG1pZGRsZXdhcmVEYXRhID0gc3RhdGUubWlkZGxld2FyZURhdGEuc2hpZnQpICE9IG51bGwgJiYgX3N0YXRlJG1pZGRsZXdhcmVEYXRhLmVuYWJsZWQueCkge1xuICAgICAgICBhdmFpbGFibGVXaWR0aCA9IG1heGltdW1DbGlwcGluZ1dpZHRoO1xuICAgICAgfVxuICAgICAgaWYgKChfc3RhdGUkbWlkZGxld2FyZURhdGEyID0gc3RhdGUubWlkZGxld2FyZURhdGEuc2hpZnQpICE9IG51bGwgJiYgX3N0YXRlJG1pZGRsZXdhcmVEYXRhMi5lbmFibGVkLnkpIHtcbiAgICAgICAgYXZhaWxhYmxlSGVpZ2h0ID0gbWF4aW11bUNsaXBwaW5nSGVpZ2h0O1xuICAgICAgfVxuICAgICAgaWYgKG5vU2hpZnQgJiYgIWFsaWdubWVudCkge1xuICAgICAgICBjb25zdCB4TWluID0gbWF4KG92ZXJmbG93LmxlZnQsIDApO1xuICAgICAgICBjb25zdCB4TWF4ID0gbWF4KG92ZXJmbG93LnJpZ2h0LCAwKTtcbiAgICAgICAgY29uc3QgeU1pbiA9IG1heChvdmVyZmxvdy50b3AsIDApO1xuICAgICAgICBjb25zdCB5TWF4ID0gbWF4KG92ZXJmbG93LmJvdHRvbSwgMCk7XG4gICAgICAgIGlmIChpc1lBeGlzKSB7XG4gICAgICAgICAgYXZhaWxhYmxlV2lkdGggPSB3aWR0aCAtIDIgKiAoeE1pbiAhPT0gMCB8fCB4TWF4ICE9PSAwID8geE1pbiArIHhNYXggOiBtYXgob3ZlcmZsb3cubGVmdCwgb3ZlcmZsb3cucmlnaHQpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBhdmFpbGFibGVIZWlnaHQgPSBoZWlnaHQgLSAyICogKHlNaW4gIT09IDAgfHwgeU1heCAhPT0gMCA/IHlNaW4gKyB5TWF4IDogbWF4KG92ZXJmbG93LnRvcCwgb3ZlcmZsb3cuYm90dG9tKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGF3YWl0IGFwcGx5KHtcbiAgICAgICAgLi4uc3RhdGUsXG4gICAgICAgIGF2YWlsYWJsZVdpZHRoLFxuICAgICAgICBhdmFpbGFibGVIZWlnaHRcbiAgICAgIH0pO1xuICAgICAgY29uc3QgbmV4dERpbWVuc2lvbnMgPSBhd2FpdCBwbGF0Zm9ybS5nZXREaW1lbnNpb25zKGVsZW1lbnRzLmZsb2F0aW5nKTtcbiAgICAgIGlmICh3aWR0aCAhPT0gbmV4dERpbWVuc2lvbnMud2lkdGggfHwgaGVpZ2h0ICE9PSBuZXh0RGltZW5zaW9ucy5oZWlnaHQpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICByZXNldDoge1xuICAgICAgICAgICAgcmVjdHM6IHRydWVcbiAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICB9XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuICB9O1xufTtcblxuZXhwb3J0IHsgYXJyb3csIGF1dG9QbGFjZW1lbnQsIGNvbXB1dGVQb3NpdGlvbiwgZGV0ZWN0T3ZlcmZsb3csIGZsaXAsIGhpZGUsIGlubGluZSwgbGltaXRTaGlmdCwgb2Zmc2V0LCBzaGlmdCwgc2l6ZSB9O1xuIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./node_modules/@floating-ui/core/dist/floating-ui.core.mjs\n\n}"); /***/ }), @@ -26,7 +26,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa \****************************************************************/ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { -eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ arrow: () => (/* binding */ arrow),\n/* harmony export */ autoPlacement: () => (/* binding */ autoPlacement),\n/* harmony export */ autoUpdate: () => (/* binding */ autoUpdate),\n/* harmony export */ computePosition: () => (/* binding */ computePosition),\n/* harmony export */ detectOverflow: () => (/* binding */ detectOverflow),\n/* harmony export */ flip: () => (/* binding */ flip),\n/* harmony export */ getOverflowAncestors: () => (/* reexport safe */ _floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getOverflowAncestors),\n/* harmony export */ hide: () => (/* binding */ hide),\n/* harmony export */ inline: () => (/* binding */ inline),\n/* harmony export */ limitShift: () => (/* binding */ limitShift),\n/* harmony export */ offset: () => (/* binding */ offset),\n/* harmony export */ platform: () => (/* binding */ platform),\n/* harmony export */ shift: () => (/* binding */ shift),\n/* harmony export */ size: () => (/* binding */ size)\n/* harmony export */ });\n/* harmony import */ var _floating_ui_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @floating-ui/utils */ \"./node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs\");\n/* harmony import */ var _floating_ui_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @floating-ui/core */ \"./node_modules/@floating-ui/core/dist/floating-ui.core.mjs\");\n/* harmony import */ var _floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @floating-ui/utils/dom */ \"./node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs\");\n\n\n\n\n\nfunction getCssDimensions(element) {\n const css = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(element);\n // In testing environments, the `width` and `height` properties are empty\n // strings for SVG elements, returning NaN. Fallback to `0` in this case.\n let width = parseFloat(css.width) || 0;\n let height = parseFloat(css.height) || 0;\n const hasOffset = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(element);\n const offsetWidth = hasOffset ? element.offsetWidth : width;\n const offsetHeight = hasOffset ? element.offsetHeight : height;\n const shouldFallback = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.round)(width) !== offsetWidth || (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.round)(height) !== offsetHeight;\n if (shouldFallback) {\n width = offsetWidth;\n height = offsetHeight;\n }\n return {\n width,\n height,\n $: shouldFallback\n };\n}\n\nfunction unwrapElement(element) {\n return !(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(element) ? element.contextElement : element;\n}\n\nfunction getScale(element) {\n const domElement = unwrapElement(element);\n if (!(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(domElement)) {\n return (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(1);\n }\n const rect = domElement.getBoundingClientRect();\n const {\n width,\n height,\n $\n } = getCssDimensions(domElement);\n let x = ($ ? (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.round)(rect.width) : rect.width) / width;\n let y = ($ ? (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.round)(rect.height) : rect.height) / height;\n\n // 0, NaN, or Infinity should always fallback to 1.\n\n if (!x || !Number.isFinite(x)) {\n x = 1;\n }\n if (!y || !Number.isFinite(y)) {\n y = 1;\n }\n return {\n x,\n y\n };\n}\n\nconst noOffsets = /*#__PURE__*/(0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(0);\nfunction getVisualOffsets(element) {\n const win = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(element);\n if (!(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isWebKit)() || !win.visualViewport) {\n return noOffsets;\n }\n return {\n x: win.visualViewport.offsetLeft,\n y: win.visualViewport.offsetTop\n };\n}\nfunction shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n if (!floatingOffsetParent || isFixed && floatingOffsetParent !== (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(element)) {\n return false;\n }\n return isFixed;\n}\n\nfunction getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n const clientRect = element.getBoundingClientRect();\n const domElement = unwrapElement(element);\n let scale = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(1);\n if (includeScale) {\n if (offsetParent) {\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(offsetParent)) {\n scale = getScale(offsetParent);\n }\n } else {\n scale = getScale(element);\n }\n }\n const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(0);\n let x = (clientRect.left + visualOffsets.x) / scale.x;\n let y = (clientRect.top + visualOffsets.y) / scale.y;\n let width = clientRect.width / scale.x;\n let height = clientRect.height / scale.y;\n if (domElement) {\n const win = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(domElement);\n const offsetWin = offsetParent && (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(offsetParent) ? (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(offsetParent) : offsetParent;\n let currentWin = win;\n let currentIFrame = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getFrameElement)(currentWin);\n while (currentIFrame && offsetParent && offsetWin !== currentWin) {\n const iframeScale = getScale(currentIFrame);\n const iframeRect = currentIFrame.getBoundingClientRect();\n const css = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(currentIFrame);\n const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;\n const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;\n x *= iframeScale.x;\n y *= iframeScale.y;\n width *= iframeScale.x;\n height *= iframeScale.y;\n x += left;\n y += top;\n currentWin = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(currentIFrame);\n currentIFrame = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getFrameElement)(currentWin);\n }\n }\n return (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)({\n width,\n height,\n x,\n y\n });\n}\n\n// If has a CSS width greater than the viewport, then this will be\n// incorrect for RTL.\nfunction getWindowScrollBarX(element, rect) {\n const leftScroll = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeScroll)(element).scrollLeft;\n if (!rect) {\n return getBoundingClientRect((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(element)).left + leftScroll;\n }\n return rect.left + leftScroll;\n}\n\nfunction getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {\n if (ignoreScrollbarX === void 0) {\n ignoreScrollbarX = false;\n }\n const htmlRect = documentElement.getBoundingClientRect();\n const x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 :\n // RTL scrollbar.\n getWindowScrollBarX(documentElement, htmlRect));\n const y = htmlRect.top + scroll.scrollTop;\n return {\n x,\n y\n };\n}\n\nfunction convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {\n let {\n elements,\n rect,\n offsetParent,\n strategy\n } = _ref;\n const isFixed = strategy === 'fixed';\n const documentElement = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(offsetParent);\n const topLayer = elements ? (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isTopLayer)(elements.floating) : false;\n if (offsetParent === documentElement || topLayer && isFixed) {\n return rect;\n }\n let scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n let scale = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(1);\n const offsets = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(0);\n const isOffsetParentAnElement = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(offsetParent);\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeName)(offsetParent) !== 'body' || (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isOverflowElement)(documentElement)) {\n scroll = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeScroll)(offsetParent);\n }\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(offsetParent)) {\n const offsetRect = getBoundingClientRect(offsetParent);\n scale = getScale(offsetParent);\n offsets.x = offsetRect.x + offsetParent.clientLeft;\n offsets.y = offsetRect.y + offsetParent.clientTop;\n }\n }\n const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(0);\n return {\n width: rect.width * scale.x,\n height: rect.height * scale.y,\n x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x,\n y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y\n };\n}\n\nfunction getClientRects(element) {\n return Array.from(element.getClientRects());\n}\n\n// Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable.\nfunction getDocumentRect(element) {\n const html = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(element);\n const scroll = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeScroll)(element);\n const body = element.ownerDocument.body;\n const width = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.max)(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth);\n const height = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.max)(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);\n let x = -scroll.scrollLeft + getWindowScrollBarX(element);\n const y = -scroll.scrollTop;\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(body).direction === 'rtl') {\n x += (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.max)(html.clientWidth, body.clientWidth) - width;\n }\n return {\n width,\n height,\n x,\n y\n };\n}\n\nfunction getViewportRect(element, strategy) {\n const win = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(element);\n const html = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(element);\n const visualViewport = win.visualViewport;\n let width = html.clientWidth;\n let height = html.clientHeight;\n let x = 0;\n let y = 0;\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n const visualViewportBased = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isWebKit)();\n if (!visualViewportBased || visualViewportBased && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n return {\n width,\n height,\n x,\n y\n };\n}\n\nconst absoluteOrFixed = /*#__PURE__*/new Set(['absolute', 'fixed']);\n// Returns the inner client rect, subtracting scrollbars if present.\nfunction getInnerBoundingClientRect(element, strategy) {\n const clientRect = getBoundingClientRect(element, true, strategy === 'fixed');\n const top = clientRect.top + element.clientTop;\n const left = clientRect.left + element.clientLeft;\n const scale = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(element) ? getScale(element) : (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(1);\n const width = element.clientWidth * scale.x;\n const height = element.clientHeight * scale.y;\n const x = left * scale.x;\n const y = top * scale.y;\n return {\n width,\n height,\n x,\n y\n };\n}\nfunction getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {\n let rect;\n if (clippingAncestor === 'viewport') {\n rect = getViewportRect(element, strategy);\n } else if (clippingAncestor === 'document') {\n rect = getDocumentRect((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(element));\n } else if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(clippingAncestor)) {\n rect = getInnerBoundingClientRect(clippingAncestor, strategy);\n } else {\n const visualOffsets = getVisualOffsets(element);\n rect = {\n x: clippingAncestor.x - visualOffsets.x,\n y: clippingAncestor.y - visualOffsets.y,\n width: clippingAncestor.width,\n height: clippingAncestor.height\n };\n }\n return (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.rectToClientRect)(rect);\n}\nfunction hasFixedPositionAncestor(element, stopNode) {\n const parentNode = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getParentNode)(element);\n if (parentNode === stopNode || !(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(parentNode) || (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isLastTraversableNode)(parentNode)) {\n return false;\n }\n return (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);\n}\n\n// A \"clipping ancestor\" is an `overflow` element with the characteristic of\n// clipping (or hiding) child elements. This returns all clipping ancestors\n// of the given element up the tree.\nfunction getClippingElementAncestors(element, cache) {\n const cachedResult = cache.get(element);\n if (cachedResult) {\n return cachedResult;\n }\n let result = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getOverflowAncestors)(element, [], false).filter(el => (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(el) && (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeName)(el) !== 'body');\n let currentContainingBlockComputedStyle = null;\n const elementIsFixed = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(element).position === 'fixed';\n let currentNode = elementIsFixed ? (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getParentNode)(element) : element;\n\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n while ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(currentNode) && !(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isLastTraversableNode)(currentNode)) {\n const computedStyle = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(currentNode);\n const currentNodeIsContaining = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isContainingBlock)(currentNode);\n if (!currentNodeIsContaining && computedStyle.position === 'fixed') {\n currentContainingBlockComputedStyle = null;\n }\n const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && absoluteOrFixed.has(currentContainingBlockComputedStyle.position) || (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isOverflowElement)(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);\n if (shouldDropCurrentNode) {\n // Drop non-containing blocks.\n result = result.filter(ancestor => ancestor !== currentNode);\n } else {\n // Record last containing block for next iteration.\n currentContainingBlockComputedStyle = computedStyle;\n }\n currentNode = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getParentNode)(currentNode);\n }\n cache.set(element, result);\n return result;\n}\n\n// Gets the maximum area that the element is visible in due to any number of\n// clipping ancestors.\nfunction getClippingRect(_ref) {\n let {\n element,\n boundary,\n rootBoundary,\n strategy\n } = _ref;\n const elementClippingAncestors = boundary === 'clippingAncestors' ? (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isTopLayer)(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary);\n const clippingAncestors = [...elementClippingAncestors, rootBoundary];\n const firstClippingAncestor = clippingAncestors[0];\n const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {\n const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);\n accRect.top = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.max)(rect.top, accRect.top);\n accRect.right = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.min)(rect.right, accRect.right);\n accRect.bottom = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.min)(rect.bottom, accRect.bottom);\n accRect.left = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.max)(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));\n return {\n width: clippingRect.right - clippingRect.left,\n height: clippingRect.bottom - clippingRect.top,\n x: clippingRect.left,\n y: clippingRect.top\n };\n}\n\nfunction getDimensions(element) {\n const {\n width,\n height\n } = getCssDimensions(element);\n return {\n width,\n height\n };\n}\n\nfunction getRectRelativeToOffsetParent(element, offsetParent, strategy) {\n const isOffsetParentAnElement = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(offsetParent);\n const documentElement = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(offsetParent);\n const isFixed = strategy === 'fixed';\n const rect = getBoundingClientRect(element, true, isFixed, offsetParent);\n let scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n const offsets = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(0);\n\n // If the scrollbar appears on the left (e.g. RTL systems). Use\n // Firefox with layout.scrollbar.side = 3 in about:config to test this.\n function setLeftRTLScrollbarOffset() {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeName)(offsetParent) !== 'body' || (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isOverflowElement)(documentElement)) {\n scroll = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getNodeScroll)(offsetParent);\n }\n if (isOffsetParentAnElement) {\n const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent);\n offsets.x = offsetRect.x + offsetParent.clientLeft;\n offsets.y = offsetRect.y + offsetParent.clientTop;\n } else if (documentElement) {\n setLeftRTLScrollbarOffset();\n }\n }\n if (isFixed && !isOffsetParentAnElement && documentElement) {\n setLeftRTLScrollbarOffset();\n }\n const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.createCoords)(0);\n const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x;\n const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y;\n return {\n x,\n y,\n width: rect.width,\n height: rect.height\n };\n}\n\nfunction isStaticPositioned(element) {\n return (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(element).position === 'static';\n}\n\nfunction getTrueOffsetParent(element, polyfill) {\n if (!(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(element) || (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(element).position === 'fixed') {\n return null;\n }\n if (polyfill) {\n return polyfill(element);\n }\n let rawOffsetParent = element.offsetParent;\n\n // Firefox returns the element as the offsetParent if it's non-static,\n // while Chrome and Safari return the element. The element must\n // be used to perform the correct calculations even if the element is\n // non-static.\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(element) === rawOffsetParent) {\n rawOffsetParent = rawOffsetParent.ownerDocument.body;\n }\n return rawOffsetParent;\n}\n\n// Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\nfunction getOffsetParent(element, polyfill) {\n const win = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getWindow)(element);\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isTopLayer)(element)) {\n return win;\n }\n if (!(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isHTMLElement)(element)) {\n let svgOffsetParent = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getParentNode)(element);\n while (svgOffsetParent && !(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isLastTraversableNode)(svgOffsetParent)) {\n if ((0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement)(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) {\n return svgOffsetParent;\n }\n svgOffsetParent = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getParentNode)(svgOffsetParent);\n }\n return win;\n }\n let offsetParent = getTrueOffsetParent(element, polyfill);\n while (offsetParent && (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isTableElement)(offsetParent) && isStaticPositioned(offsetParent)) {\n offsetParent = getTrueOffsetParent(offsetParent, polyfill);\n }\n if (offsetParent && (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isLastTraversableNode)(offsetParent) && isStaticPositioned(offsetParent) && !(0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isContainingBlock)(offsetParent)) {\n return win;\n }\n return offsetParent || (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getContainingBlock)(element) || win;\n}\n\nconst getElementRects = async function (data) {\n const getOffsetParentFn = this.getOffsetParent || getOffsetParent;\n const getDimensionsFn = this.getDimensions;\n const floatingDimensions = await getDimensionsFn(data.floating);\n return {\n reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy),\n floating: {\n x: 0,\n y: 0,\n width: floatingDimensions.width,\n height: floatingDimensions.height\n }\n };\n};\n\nfunction isRTL(element) {\n return (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getComputedStyle)(element).direction === 'rtl';\n}\n\nconst platform = {\n convertOffsetParentRelativeRectToViewportRelativeRect,\n getDocumentElement: _floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement,\n getClippingRect,\n getOffsetParent,\n getElementRects,\n getClientRects,\n getDimensions,\n getScale,\n isElement: _floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.isElement,\n isRTL\n};\n\nfunction rectsAreEqual(a, b) {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;\n}\n\n// https://samthor.au/2021/observing-dom/\nfunction observeMove(element, onMove) {\n let io = null;\n let timeoutId;\n const root = (0,_floating_ui_utils_dom__WEBPACK_IMPORTED_MODULE_2__.getDocumentElement)(element);\n function cleanup() {\n var _io;\n clearTimeout(timeoutId);\n (_io = io) == null || _io.disconnect();\n io = null;\n }\n function refresh(skip, threshold) {\n if (skip === void 0) {\n skip = false;\n }\n if (threshold === void 0) {\n threshold = 1;\n }\n cleanup();\n const elementRectForRootMargin = element.getBoundingClientRect();\n const {\n left,\n top,\n width,\n height\n } = elementRectForRootMargin;\n if (!skip) {\n onMove();\n }\n if (!width || !height) {\n return;\n }\n const insetTop = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.floor)(top);\n const insetRight = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.floor)(root.clientWidth - (left + width));\n const insetBottom = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.floor)(root.clientHeight - (top + height));\n const insetLeft = (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.floor)(left);\n const rootMargin = -insetTop + \"px \" + -insetRight + \"px \" + -insetBottom + \"px \" + -insetLeft + \"px\";\n const options = {\n rootMargin,\n threshold: (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.max)(0, (0,_floating_ui_core__WEBPACK_IMPORTED_MODULE_0__.min)(1, threshold)) || 1\n };\n let isFirstUpdate = true;\n function handleObserve(entries) {\n const ratio = entries[0].intersectionRatio;\n if (ratio !== threshold) {\n if (!isFirstUpdate) {\n return refresh();\n }\n if (!ratio) {\n // If the reference is clipped, the ratio is 0. Throttle the refresh\n // to prevent an infinite loop of updates.\n timeoutId = setTimeout(() => {\n refresh(false, 1e-7);\n }, 1000);\n } else {\n refresh(false, ratio);\n }\n }\n if (ratio === 1 && !rectsAreEqual(elementRectForRootMargin, element.getBoundingClientRect())) {\n // It's possible that even though the ratio is reported as 1, the\n // element is not actually fully within the IntersectionObserver's root\n // area anymore. This can happen under performance constraints. This may\n // be a bug in the browser's IntersectionObserver implementation. To\n // work around this, we compare the element's bounding rect now with\n // what it was at the time we created the IntersectionObserver. If they\n // are not equal then the element moved, so we refresh.\n refresh();\n }\n isFirstUpdate = false;\n }\n\n // Older browsers don't support a `document` as the root and will throw an\n // error.\n try {\n io = new IntersectionObserver(handleObserve, {\n ...options,\n // Handle