diff --git a/.github/workflows/test_only.yml b/.github/workflows/test_only.yml new file mode 100644 index 0000000..d1f9c57 --- /dev/null +++ b/.github/workflows/test_only.yml @@ -0,0 +1,16 @@ +name : Tests +on: + push: + branches: + - vlad/devel + - main + - staging +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build Docker Image + run: docker compose --profile test build tests + - name: Run Tests + run: docker compose --profile test run --rm tests diff --git a/.gitignore b/.gitignore index c58c411..454bfcd 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,7 @@ cython_debug/ #.idea/ #.kate-swp + +.vscode/ +time_taken_for_various* + diff --git a/README.md b/README.md index 5a09e58..c9f993c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # web-app - Currently served at: http://193.196.39.120:8080 -- Test server: http://193.196.39.193:8080 +- Test server: http://193.196.39.84:8080 diff --git a/app.py b/app.py index 8a62e17..aa673bc 100644 --- a/app.py +++ b/app.py @@ -4,7 +4,7 @@ import dash_bootstrap_components as dbc # Usage Locally: -# $ python app.py +# $ poetry run python app.py # select theme # https://dash-bootstrap-components.opensource.faculty.ai/docs/themes/ @@ -12,20 +12,41 @@ server = flask.Flask(__name__) -app = Dash(__name__, external_stylesheets=external_stylesheets, server=server, use_pages=True) # , suppress_callback_exceptions=True +app = Dash( + __name__, + external_stylesheets=external_stylesheets, + server=server, + use_pages=True, + serve_locally=True, + suppress_callback_exceptions=True # this is needed because renderer generate callbacks dynamically with per instance uuids +) app.layout = dbc.Container([ - html.H1('ISAS Interactive'), - dbc.Nav([ - dbc.NavLink(html.Div(f"{page['name'].lower()}"), href=page["relative_path"], active="exact") - for page in dash.page_registry.values() - ], pills=True, className='bg-light'), - html.P(), - dash.page_container -], fluid=True) + html.H1('ISAS Interactive', + style={ + "caretColor": "transparent", + "userSelect": "none" + } + ), + dbc.Nav([ + dbc.NavLink( + html.Div( + f"{page['name'].lower()}"), + href=page["relative_path"], + active="exact", + className='rounded-3' + ) + for page in dash.page_registry.values() + ], pills=True, className='bg-light rounded-3'), + html.P(), + html.Div(dash.page_container, className="flex-grow-1 d-flex flex-column", id="outer-page-container", style={'minHeight': '0'}) +], +fluid=True, +className="vh-100 d-flex flex-column" +) if __name__ == '__main__': - # processes=6, threaded=False, - app.run(debug=True, threaded=True, host='0.0.0.0', port='8080') - # app.run(debug=True, processes=6, threaded=False, host='0.0.0.0', port='8080') - # app.run(debug=True) + # processes=6, threaded=False, + app.run(debug=True, threaded=True, host='0.0.0.0', port='8080') + # app.run(debug=True, processes=6, threaded=False, host='0.0.0.0', port='8080') + # app.run(debug=True) diff --git a/assets/blockquote.css b/assets/blockquote.css new file mode 100644 index 0000000..76e7b54 --- /dev/null +++ b/assets/blockquote.css @@ -0,0 +1,23 @@ +/* Danger / error-style blockquote inside dcc.Markdown */ +blockquote { + border-left: 4px solid #e53935; + background-color: #ffebee; + padding: 1rem 1.25rem 1rem 2.7rem; + margin: 1rem 0; + color: #b71c1c; + border-radius: 6px; + position: relative; +} + +/* Warning icon */ +blockquote::before { + content: "⚠"; + position: absolute; + left: 0.9rem; + top: 0.9rem; + font-size: 1.1rem; +} + +blockquote p { + margin: 0; +} diff --git a/assets/dpr.js b/assets/dpr.js new file mode 100644 index 0000000..e8376bb --- /dev/null +++ b/assets/dpr.js @@ -0,0 +1,7 @@ +window.dash_clientside = Object.assign({}, window.dash_clientside, { + utils: { + getDevicePixelRatio: function(_) { + return window.devicePixelRatio || 1; + } + } +}); \ No newline at end of file diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000..824a924 Binary files /dev/null and b/assets/favicon.ico differ diff --git a/assets/full_page.css b/assets/full_page.css new file mode 100644 index 0000000..349cc9e --- /dev/null +++ b/assets/full_page.css @@ -0,0 +1,45 @@ +/* Dash puts pages inside 2 divs that are not styleable otherwise */ +#outer-page-container > * { /* this is the empty div dash puts pages inside */ + flex-grow: 1; + display: flex; + flex-direction: column; + min-height: 0; +} +#_pages_content { + flex-grow: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + + +#panelgroup-parent-container { + min-height: 0; +} + +.split-pane-group { + height: 100%; +} + +@media (min-width: 768px) { + .split-pane-handle { + cursor: col-resize; + } +} + +/* < md: "mobile" layout */ +@media (max-width: 767.98px) { + .split-pane-group { + flex-direction: column !important; + } + + + .split-pane-left .bg-light { + max-height: 40vh; + overflow-y: auto; + } + + .split-pane-handle { + display: none !important; /* no resize on mobile */ + } +} diff --git a/assets/tooltip.js b/assets/tooltip.js index 8472412..241acaa 100644 --- a/assets/tooltip.js +++ b/assets/tooltip.js @@ -1,8 +1,44 @@ window.dccFunctions = window.dccFunctions || {}; window.dccFunctions.trafo_L = function(value) { - if (value < Math.log10(1.25)) { - return 0; - } else { - return Math.round(Math.pow(10, value)); - } + if (value < Math.log10(1.25)) { + return 0; + } else { + return Math.round(Math.pow(10, value)); + } +} +window.dccFunctions.transform_log_nice = function(value) { + let x = Math.pow(10, value); + // same as transform_up in log_slider.py + if (x == 0){ + return 0; + } + const sign = Math.sign(x); + x = Math.abs(x); + + let step = Math.pow(10, Math.floor(Math.log10(x))) / 10; + let nice_value = sign * Math.round(x / step) * step; + nice_value = Number(nice_value.toFixed(4)); + return nice_value; +} + +window.dccFunctions.transform_fib = function(value) { + function fibonacci(n) { + return n < 1 ? 0 + : n <= 2 ? 1 + : fibonacci(n - 1) + fibonacci(n - 2) + } + return fibonacci(value); +} + +window.dccFunctions.transform_fib_m1 = function(value) { + function fibonacci(n) { + return n < 1 ? 0 + : n <= 2 ? 1 + : fibonacci(n - 1) + fibonacci(n - 2) + } + return fibonacci(value) - 1; +} + +window.dccFunctions.transform_square = function(value) { + return value * value; } \ No newline at end of file diff --git a/components/__init__.py b/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/label.py b/components/label.py new file mode 100644 index 0000000..6078548 --- /dev/null +++ b/components/label.py @@ -0,0 +1,8 @@ +from dash import html + +# wrapps component in a label +def Label(label, component): + return html.Div([ + html.Label(label), + component, + ]) \ No newline at end of file diff --git a/components/popup_box.py b/components/popup_box.py new file mode 100644 index 0000000..bdf8d67 --- /dev/null +++ b/components/popup_box.py @@ -0,0 +1,48 @@ +import dash_bootstrap_components as dbc +from dash import Dash, html, dcc, Input, Output, callback, State + +def PopupBox(id, label, title, children): + @callback( + Output(f"modal-{label}-{id}", "is_open"), + Input(f"popup-box-{label}-{id}", "n_clicks"), + Input(f"close-{label}-{id}", "n_clicks"), + State(f"modal-{label}-{id}", "is_open"), + ) + def toggle_modal(n1, n2, is_open): + if n1 or n2: + return not is_open + return is_open + + + button = html.Div( + children=[ + dbc.Button(label, id=f"popup-box-{label}-{id}", n_clicks=0) + ], + style={ + "caretColor": "transparent", + "userSelect": "none" + } + ) + + modal = dbc.Modal( + id=f"modal-{label}-{id}", + is_open=False, + children=[ + html.Div( + children=[dbc.ModalHeader(dbc.ModalTitle(title), close_button=True)], + style={ + "caretColor": "transparent", + "userSelect": "none" + } + ), + dbc.ModalBody(children), + dbc.ModalFooter( + dbc.Button("Close", id=f"close-{label}-{id}", n_clicks=0) + ), + ], + size="xl", + centered=True, + scrollable=True, + ) + return button, modal + diff --git a/components/split_pane.py b/components/split_pane.py new file mode 100644 index 0000000..2121dcc --- /dev/null +++ b/components/split_pane.py @@ -0,0 +1,52 @@ +from dash import html +from dash_resizable_panels import PanelGroup, Panel, PanelResizeHandle +import dash_bootstrap_components as dbc + + + +def SplitPane(children1, children2, default_size): + return html.Div([ + PanelGroup( + id='panel-group', + children=[ + Panel( + id='left-sidebar', + className="split-pane-left", + minSizePercentage=15, + defaultSizePercentage=default_size, + children=[ + html.Div( + children1, + className="bg-light h-100 w-100 rounded-3 p-3 pb-5", style={'overflowY': 'scroll', 'overflowX': 'hidden', 'minHeight': '0'}, + ) + ], + ), + PanelResizeHandle( + id='resize-handle', + className="split-pane-handle", + style={ + "flex": "0 0 20px", + "margin": "0 -10px", + "background": "transparent", + "cursor": "col-resize", + "userSelect": "none", + "position": "relative", + "zIndex": 2, + }, + ), + Panel( + id='plot-panel', + className="split-pane-right", + minSizePercentage=15, + children=children2, + ) + ], + direction='horizontal', + className='split-pane-group w-100 px-0 pb-2', + style={'minHeight': '0'} + ) + ], + className='px-0 flex-grow-1 d-flex flex-column', + id='pangelgroup-parent-container', + style={'minHeight': '0'} + ) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..545e414 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +services: + web: + build: + context: . + target: prod + image: web_app:latest + ports: + - "8080:8080" + restart: unless-stopped + + tests: + build: + context: . + target: tests + image: web_app:tests + profiles: ["test"] diff --git a/dockerfile b/dockerfile index 7ffee44..add5fb8 100644 --- a/dockerfile +++ b/dockerfile @@ -1,14 +1,26 @@ -FROM python:3.10.12 +FROM python:3.10.12 AS base WORKDIR /code -COPY ./requirements.txt /code/requirements.txt - -RUN pip install --upgrade pip -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +ARG POETRY_VERSION=2.3.1 +RUN pip install --no-cache-dir "poetry==${POETRY_VERSION}" +COPY pyproject.toml poetry.lock /code/ +RUN poetry sync --no-root --without dev COPY ./app.py /code/app.py COPY ./assets /code/assets COPY ./pages /code/pages +COPY ./util /code/util +COPY ./components /code/components +COPY ./renderer /code/renderer +COPY ./model /code/model + + +FROM base AS tests +RUN apt-get update && apt-get install -y chromium chromium-driver +RUN poetry sync --no-root --with dev +COPY ./tests /code/tests +CMD ["poetry", "run", "pytest", "--headless"] -CMD ["python","app.py"] +FROM base AS prod +CMD ["poetry", "run", "gunicorn", "--workers", "1", "--bind", "0.0.0.0:8080", "app:server"] diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/cylinder/__init__.py b/model/cylinder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/cylinder/cylinder.py b/model/cylinder/cylinder.py new file mode 100644 index 0000000..237f593 --- /dev/null +++ b/model/cylinder/cylinder.py @@ -0,0 +1,161 @@ +import numpy as np +from scipy.spatial import ConvexHull +import plotly.figure_factory as ff +from scipy.spatial import Delaunay + + +from model.distributions.distribution_loader import DistributionLoader +from model.distributions.cylinder.cylinder_distribution import CylinderDistribution +from model.manifold import Manifold +from renderer.plot_settings_2d import PlotSettings2D +from model.distributions.cylinder.uniform.fibonacci_kronecker import CylinderFibUniformSampling +from util.selectors.slider import Slider + +class Cylinder(Manifold): + def __init__(self, resolution=100, r=1): + self.xyz = self.generate_xyz(resolution, 0.998, r_height=1) + + self.distributions = DistributionLoader(CylinderDistribution, "model.distributions.cylinder").get_distributions() + + self.r = r + self.mesh_xyz = self._init_mesh() + + axes_2d = ( + np.arange(0, 2.5 * np.pi, np.pi / 2), # 0, π/2, π, 3π/2, 2π + ["0", "π/2", "π", "3π/2", "2π"] + ) + self.plot_settings_2d = PlotSettings2D( + axes_2d_x=axes_2d, + axes_2d_y=axes_2d, + lock_aspect_ratio=False, + periodic_x=True, + periodic_y=False, + periodic_x_amount=2 * np.pi, + x_title="p", + y_title="z", + reverse_x_y_axis=False, + color_location=(0,0,2*np.pi, 2*np.pi), + ) + + self.camera_settings_3d = dict( + eye=dict(x=2, y=2, z=2), + center=dict(x=0, y=0, z=0), + ) + + def generate_xyz(self, resolution=50, r=1, r_height=None): + if r_height is None: # allow pointionally setting height different from radius + r_height = r # usefull for making cylinders slightly thinner so mesh doesnt interfere + + + p = np.linspace(0, 2*np.pi, resolution) + z = np.linspace(0, 2*np.pi, resolution) + t,p = np.meshgrid(p,z) + + x = r * np.cos(t) + y = r * np.sin(t) + z = r_height * p + + + return x, y, z + + def update_sample(self, selected_distribution, selected_sampling_method, sample_options, distribution_options): + dist = self.distributions[selected_distribution] + sampling_method = dist.sampling_method_dict[selected_sampling_method] + new_sample = sampling_method.sample(sample_options, distribution_options) + + if (new_sample is None) or new_sample.size == 0: + samples = np.empty((0, 3), dtype=float) + samples_2d = np.empty((0, 2), dtype=float) + return (samples, samples_2d) + + x, y, z = self.p_z_to_xyz(new_sample[:,0], new_sample[:,1], self.r) + + samples = np.column_stack((x, y, z)) + samples_2d = new_sample + + return (samples, samples_2d) + + + @staticmethod + def p_z_to_xyz(p, z, r=1): + x = r * np.cos(p) + y = r * np.sin(p) + z = r * z + + return x, y, z + + @staticmethod + def xyz_to_p_z(x, y, z): + p = np.arctan2(y, x) % (2 * np.pi) + + return p, z + + def pdf_2d(self, xy, pdf): + x, y, z = self.p_z_to_xyz(xy[:,0], xy[:,1], self.r) + return pdf(np.column_stack((x, y, z))) + + def generate_mesh(self, pdf, alpha=1): + # mesh_xyz has nans for line segments, mask before passing to pdf + mask = np.all(np.isfinite(self.mesh_xyz), axis=-1) + dens = pdf(self.mesh_xyz[mask]) # only extrude on p, dont change z + + xyz_extruded = np.full_like(self.mesh_xyz, np.nan, dtype=float) # full of nans + + xyz_extruded[..., :2][mask] = self.mesh_xyz[..., :2][mask] * (1 + alpha * dens[:, np.newaxis]) + xyz_extruded[..., 2][mask] = self.mesh_xyz[..., 2][mask] # z unchanged + return xyz_extruded[:,0], xyz_extruded[:,1], xyz_extruded[:,2] + + def _init_mesh(self, resolution=3000): + pz = CylinderFibUniformSampling.sample(None, [Slider("Number of Samples", 10, resolution, resolution)] , []) + #pz[:, 1] = -0.1 + (pz[:, 1]) * 1.1 # extend slightly beyond [0, 2pi] so the top and bottom looks better + + # add ring at top and bottom + z_bottom = 0 + z_top = 2 * np.pi + num_points_ring = int(np.sqrt(resolution)) + for j in range(num_points_ring): + p = (j / num_points_ring) * 2 * np.pi + pz = np.vstack((pz, np.array([p, z_bottom]))) + pz = np.vstack((pz, np.array([p, z_top]))) + + x, y, z = self.p_z_to_xyz(pz[:,0], pz[:,1], self.r) + + simplices = Delaunay(pz).simplices + ok_mask = (pz[:, 0] > np.pi / 4) & (pz[:, 0] < 7 * np.pi / 4) # ugly seam + ok_idx = np.where(ok_mask)[0] + + # throw away all triangles around the seam + good_vertices = np.isin(simplices, ok_idx) + good_triangles = good_vertices.any(axis=1) + simplices = simplices[good_triangles] + + + # shift p by pi and throw away triangles at other seam, that is at the opposite side + pz2 = np.copy(pz) + pz2[:,0] = (pz2[:,0]+np.pi) % (2 * np.pi) + + simplices_2 = Delaunay(pz2).simplices + ok_mask_2 = (pz2[:, 0] > np.pi / 4) & (pz2[:, 0] < 7 * np.pi / 4) # ugly seam + ok_idx_2 = np.where(ok_mask_2)[0] + + good_vertices_2 = np.isin(simplices_2, ok_idx_2) + good_triangles_2 = good_vertices_2.any(axis=1) + simplices_2 = simplices_2[good_triangles_2] + + # merge both halves + simplices_merged = np.vstack((simplices, simplices_2)) + simplices_merged = np.unique(simplices_merged, axis=0) + + def cf(xi, yi, zi, zmin=np.min(z), zmax=np.max(z)): + if zi > zmax: + zi = np.nextafter(zmax, zmin) + return zi + + fig = ff.create_trisurf(x=x, y=y, z=z, + simplices=simplices_merged, + show_colorbar=False, + color_func=cf + ) + + arr = np.column_stack((fig.data[1].x, fig.data[1].y, fig.data[1].z)) + return np.where(arr == None, np.nan, arr).astype(float) # line segments have None in them, put them to nan so we can do mult later diff --git a/model/distribution.py b/model/distribution.py new file mode 100644 index 0000000..a02a2a8 --- /dev/null +++ b/model/distribution.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +from functools import cached_property + +class Distribution(ABC): + def __init__(self): + self.distribution_options = [] + self.sampling_methods = [] + + + # Returns the name of the distribution + @abstractmethod + def get_name(self): + pass + + @cached_property + def sampling_method_dict(self): + return {m.get_name(): m for m in self.sampling_methods} + + @abstractmethod + def get_pdf(self, distribution_options): + pass + + @property + def info_md(self): + return getattr(self, "_info_md", "") + + @info_md.setter + def info_md(self, value): + self._info_md = value \ No newline at end of file diff --git a/model/distributions/__init__.py b/model/distributions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/conditional/__init__.py b/model/distributions/conditional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/conditional/conditional.py b/model/distributions/conditional/conditional.py new file mode 100644 index 0000000..c2d094b --- /dev/null +++ b/model/distributions/conditional/conditional.py @@ -0,0 +1,235 @@ +from pathlib import Path +import plotly +import dash +from dash import dcc, html, Input, Output, callback, Patch, callback_context +import plotly.graph_objects as go +import dash_bootstrap_components as dbc +from numpy import sqrt, linspace, pi, sign, exp, square, array, diff, matmul, zeros, meshgrid +from numpy.random import randint +from numpy.linalg import det, solve + +from components.popup_box import PopupBox +from components.label import Label + + +from model.selfcontained_distribution import SelfContainedDistribution + +class Conditional(SelfContainedDistribution): + def __init__(self): + # Colors + col_marginal = plotly.colors.qualitative.Plotly[0] + col_conditional = plotly.colors.qualitative.Plotly[1] + col_slice = plotly.colors.qualitative.Plotly[4] + # axis limits + rangx = [-4, 4] + rangy = [-4, 4] + # slider range + smin = rangy[0] + smax = rangy[1] + # plot size relative to window size + relwidth = 95 + relheight = round((relwidth/diff(rangx)*diff(rangy))[0]) + + # Grid + self.xv = linspace(rangx[0], rangx[1], 100) + self.yv = linspace(rangy[0], rangy[1], 100) + self.xm, self.ym = meshgrid(self.xv, self.yv) + + self.config = { + 'toImageButtonOptions': { + 'format': 'jpeg', # png, svg, pdf, jpeg, webp + 'width': None, # None: use currently-rendered size + 'height': None, + 'filename': 'conditional', + }, + 'responsive': True, + 'scrollZoom': True, + } + + self.fig = go.Figure( + data=[ + go.Surface( + name='Joint f(x,y)', + x=self.xm, + y=self.ym, + z=self.xm*0, + hoverinfo='skip', + colorscale='Cividis', + showlegend=True, + showscale=False, + reversescale=False, + contours={ + "z": { + "show": True, + "start": 0.1, + "end": 1, + "size": 0.1, + "width": 1, + "color": "white" + } + } + ), + go.Scatter3d( + name='Marginal f(x)', + x=self.xv, + y=self.yv*0+self.yv[0], + mode='lines', + z=self.xv*0, + marker_color=col_marginal, + showlegend=True, + hoverinfo='skip', + line={'width': 10}, + # surfaceaxis=2 # wait for: https://github.com/plotly/plotly.js/issues/2352 + ), + go.Scatter3d( + name='Conditional f(x|ŷ)', + x=self.xv, y=self.yv*0+self.yv[0], + mode='lines', + z=self.xv*0, + marker_color=col_conditional, + showlegend=True, + hoverinfo='skip', + line={'width': 4}, + # surfaceaxis=2 # wait for: https://github.com/plotly/plotly.js/issues/2352 + ), + go.Scatter3d( + name='Slice f(x,ŷ)', + x=self.xv, + y=self.yv*0+self.yv[0], + mode='lines', + z=self.xv*0, + marker_color=col_slice, + showlegend=True, + hoverinfo='skip', + line={'width': 8} + ), + ] + ) + self.fig.update_xaxes(range=rangx, tickmode='array', tickvals=list(range(rangx[0], rangx[1]+1))) + self.fig.update_yaxes(range=rangy, tickmode='array', tickvals=list(range(rangy[0], rangy[1]+1)), scaleanchor="x", scaleratio=1) + self.fig.update_layout(transition_duration=100, transition_easing='linear') + self.fig.update_scenes(camera_projection_type="orthographic") + self.fig.update_scenes(aspectmode="auto") + # fig.update_scenes(xaxis_nticks=1) + # fig.update_scenes(yaxis_nticks=1) + self.fig.update_scenes(zaxis_nticks=1) + self.fig.update_layout(margin=dict(l=0, r=0, t=0, b=0, pad=0)) + + self.fig.update_layout( + legend=dict( + yanchor="top", + y=0.98, + xanchor="left", + x=0.02, + bgcolor="rgba(255,255,255,0.7)" + ) + ) + + path = Path(__file__).parent / "info_text.md" + with open(path, 'r') as f: + self.info_text = dcc.Markdown(f.read(), mathjax=True) + + self.settings_layout = [ + dbc.Container( + dbc.Col([ + html.Br(), + + # y Slider + Label("Condition on ŷ", + dcc.Slider( + id="joint-y", + min=smin, + max=smax, + value=randint(smin*10, smax*10)/10, + updatemode='drag', marks=None, + tooltip={"template": "ŷ={value}", "placement": "bottom", "always_visible": True} + ), + ), + + # ρ Slider + Label("Correlation ρ", + dcc.Slider( + id="joint-ρ", + min=-1, max=1, + value=randint(-9, 9)/10, + updatemode='mouseup', + marks=None, + tooltip={"template": "ρ={value}", "placement": "bottom", "always_visible": True} + ) + ), + + html.Hr(), + html.Br(), + + # Info Popup + *PopupBox("joint-info", "Learn More", "Additional Information", self.info_text), + + ]), + fluid=True, + className="g-0" + ), + ] + self.plot_layout = [ + dcc.Graph(id="joint-graph", figure=self.fig, config=self.config, style={'height': '100%'}), + ] + + self._register_callbacks() + + def _register_callbacks(self): + @callback( + Output("joint-graph", "figure"), + Input("joint-y", "value"), + Input("joint-ρ", "value"), + ) + def update(ys, ρ): + patched_fig = Patch() + # Joint Parameters + μ = zeros([2, 1]) + sx = 1 + sy = 1 + # TODO special treatment for singular density + ρ = sign(ρ) * min(abs(ρ), .9999) + C = array([[sx**2, sx*sy*ρ], [sx*sy*ρ, sy**2]]) + # Marginal Parameters + µMarginal = µ[0] + CMarginal = C[0, 0] + marginal_fac = 1 / self.gauss1(0, 0, CMarginal) + # Density has been modified? + if (callback_context.triggered_id == "joint-ρ") | (callback_context.triggered_id is None): + zMarginal = self.gauss1(self.xv, µMarginal, CMarginal) + patched_fig['data'][1]['z'] = zMarginal * marginal_fac + # Compute new joint density values + # TODO should be more elegant than 2 for loops + zJoint = self.xm*0 + for i in range(self.xm.shape[0]): + for j in range(self.xm.shape[1]): + zJoint[i, j] = self.gauss2(self.xm[i, j], self.ym[i, j], μ, C) + zJoint = zJoint / self.gauss2(0, 0, zeros([2, 1]), C) # rescale to height 1 + patched_fig['data'][0]['z'] = zJoint + # Compute Conditional + # https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf + µCond = µ[0] + C[0, 1] / C[1, 1] * (ys-µ[1]) + CCond = C[0, 0] - C[0, 1] / C[1, 1] * C[0, 1] + zCond = self.gauss1(self.xv, µCond, CCond) + zSlice = zCond / self.gauss1(0, 0, CCond) * self.gauss1(ys, µ[1], C[1, 1]) / self.gauss1(0, 0, C[1, 1]) + # Plot Conditional + patched_fig['data'][2]['z'] = zCond * marginal_fac + # Plot Joint Slice + patched_fig['data'][3]['y'] = self.xv*0+ys + patched_fig['data'][3]['z'] = zSlice + 1e-3 + return patched_fig + + @staticmethod + def gauss1(x, μ, C): + return 1/sqrt(2*pi*C) * exp(-1/2 * square((x-μ))/C) + + + @staticmethod + def gauss2(x, y, μ, C): + d = array([x-μ[0], y-μ[1]]) + d = d.reshape(-1, 1) # to column vector + f = 1/sqrt(det(2*pi*C)) * exp(-1/2 * matmul(d.T, solve(C, d))) + return f[0][0] + + + diff --git a/model/distributions/conditional/info_text.md b/model/distributions/conditional/info_text.md new file mode 100644 index 0000000..8863da1 --- /dev/null +++ b/model/distributions/conditional/info_text.md @@ -0,0 +1,35 @@ +## 2D Gaussian +Interactive visualizaton of the 2D Gaussian and its marginal and conditional density. + +$$ +f(\underline x) = \mathcal{N}(\underline x; \underline \mu, \textbf{C}) = +\frac{1}{2\pi \sqrt{\det(\textbf{C})}} +\cdot \exp\!\left\{ -\frac{1}{2} +\cdot (\underline x - \underline \mu)^\top \textbf{C}^{-1} (\underline x - \underline \mu) \right\} \enspace, +\quad \underline{x}\in \mathbb{R}^2 \enspace, \quad \textbf{C} \enspace \text{positive semidefinite} \enspace. +$$ + +### Formulas and Literature +The Gaussian parameters are restricted to +$$ +\underline \mu = \begin{bmatrix}0 \\ 0\end{bmatrix}\,, \quad +\textbf{C} = \begin{bmatrix}1 & \rho \\ \rho & 1\end{bmatrix} \enspace. +$$ + +Formulas for marginalization and conditioning of are given in the +[[MatrixCookbook](https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf)]. + +Note that the 1D and 2D densities are scaled with respect to each other such that 2D joint and 1D marginal have +the same height and therefore the same shape when looking on the x-z plane. + + +### Interactivity +- GUI + - rotate: left mouse click + - pan: right mouse click + - zoom: mouse wheel + - add/remove lines: click in legend +- value in state space (slider) + - value to condition on $\hat{y}$ +- density parameter (slider) + - correlation coefficient $\rho$ \ No newline at end of file diff --git a/model/distributions/cylinder/cylinder_distribution.py b/model/distributions/cylinder/cylinder_distribution.py new file mode 100644 index 0000000..318850e --- /dev/null +++ b/model/distributions/cylinder/cylinder_distribution.py @@ -0,0 +1,26 @@ +from abc import ABC, abstractmethod +from functools import cached_property +from model.distribution import Distribution + + +class CylinderDistribution(Distribution): + def __init__(self): + self.distribution_options = [] + # a list of objects that implement the cylinder_sampling_schema interface + # that can be used with this distribution + self.sampling_methods = [] + + + # Returns the name of the distribution + @abstractmethod + def get_name(self): + pass + + @cached_property + def sampling_method_dict(self): + return {m.get_name(): m for m in self.sampling_methods} + + # returns a functions that takes a (N, 3) shape nparray and returns the pdf value at the points + @abstractmethod + def get_pdf(self, distribution_options): + pass \ No newline at end of file diff --git a/model/distributions/cylinder/cylinder_sampling_schema.py b/model/distributions/cylinder/cylinder_sampling_schema.py new file mode 100644 index 0000000..6f69037 --- /dev/null +++ b/model/distributions/cylinder/cylinder_sampling_schema.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod +from model.sampling_schema import SamplingSchema +class CylinderSamplingSchema(SamplingSchema): + def __init__(self): + self.sample_options = [] + + # Returns the name of the sampling method + @abstractmethod + def get_name(self): + pass + + # returns samples as a numpy array of shape (n, 2) + # where (2) are the parameters (p, z) (angle, height) on the torus + @abstractmethod + def sample(self, sample_options, distribution_options): + pass \ No newline at end of file diff --git a/model/distributions/cylinder/partially_wraped_normal/__init__.py b/model/distributions/cylinder/partially_wraped_normal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/cylinder/partially_wraped_normal/cartesian.py b/model/distributions/cylinder/partially_wraped_normal/cartesian.py new file mode 100644 index 0000000..cac93f2 --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/cartesian.py @@ -0,0 +1,51 @@ +from abc import ABC, abstractmethod +import numpy as np +from scipy.stats import norm + +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_square import SliderSquare +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu +from util.cartesian_util import CartesianUtil as cu + +class CylinderFibCartPWNSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 16, 100, 4)) + ] + self.sampler = CylinderFibRank1UniformSampling() + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + > It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + # see https://isas.iar.kit.edu/pdf/Fusion21_Frisch.pdf + sample_count = sample_options[0].state + + n = int(np.sqrt(sample_count)) + + grid = cu.generate_cartesian_grid(n, (1.0, 1.0)) + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.transform_grid_gaussian(grid, (mean_x, mean_y), Cov) + + # wrap + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + return gaus_grid + + + diff --git a/model/distributions/cylinder/partially_wraped_normal/fibonacci_frolov.py b/model/distributions/cylinder/partially_wraped_normal/fibonacci_frolov.py new file mode 100644 index 0000000..42e6550 --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/fibonacci_frolov.py @@ -0,0 +1,42 @@ +import numpy as np +from deterministic_gaussian_sampling_fibonacci import sample_gaussian_fibonacci + +from util.selectors.silder_log import LogSlider +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class FFrolovPWNSampling(CylinderSamplingSchema): + def __init__(self): + def _check_input(val): + return val >= 1 and val <= 100003 and isinstance(val, int) + + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Fibonacci Frolov" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + mu = np.array([mean_x, mean_y]) + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.sample_frolov_gaussian(mu, Cov, sample_count, "Fibonacci") + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/cylinder/partially_wraped_normal/fibonacci_kronecker.py b/model/distributions/cylinder/partially_wraped_normal/fibonacci_kronecker.py new file mode 100644 index 0000000..268c5be --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/fibonacci_kronecker.py @@ -0,0 +1,51 @@ +import numpy as np + +from util.selectors.silder_log import LogSlider +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class CylinderFibKroneckerPWNSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Fibonacci-Kronecker Lattice" + + def sample(self, sample_options, distribution_options): + # see https://isas.iar.kit.edu/pdf/Fusion21_Frisch.pdf + sample_count = sample_options[0].state + + indices = np.arange(0, sample_count) + indices_p1 = np.arange(0, sample_count + 1) + gol = (1+5**0.5)/2 + + # centered rank-1 lattice generator + equidistant_generator = (2 * indices + 1) / (2 * sample_count) + + t = equidistant_generator + p = (indices_p1 / gol) % 1 + p = p[1:] + + + fib_grid = np.column_stack((t , p)) + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.transform_grid_gaussian(fib_grid, (mean_x, mean_y), Cov) + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/cylinder/partially_wraped_normal/fibonacci_rank_1.py b/model/distributions/cylinder/partially_wraped_normal/fibonacci_rank_1.py new file mode 100644 index 0000000..e69a026 --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/fibonacci_rank_1.py @@ -0,0 +1,48 @@ +from abc import ABC, abstractmethod +import numpy as np +from scipy.stats import norm + +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_fib import SliderFib +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + +class CylinderFibRank1PWNSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderFib("Number of Samples", 3, 33, 21, 9, minus_1=True)) + ] + self.sampler = CylinderFibRank1UniformSampling() + + def get_name(self): + return "Fibonacci-Rank-1 Lattice" + + def sample(self, sample_options, distribution_options): + # see https://isas.iar.kit.edu/pdf/Fusion21_Frisch.pdf + sample_count = sample_options[0].state + 1 # because minus_1 is true slider displays fib(n)-1 + + t, p = self.sampler.get_rank_1(sample_count, sample_options[0].idx, without_first_point=True) + + fib_grid = np.column_stack((t , p)) + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.transform_grid_gaussian(fib_grid, (mean_x, mean_y), Cov) + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + + return gaus_grid + + + \ No newline at end of file diff --git a/model/distributions/cylinder/partially_wraped_normal/frolov.py b/model/distributions/cylinder/partially_wraped_normal/frolov.py new file mode 100644 index 0000000..3b6addd --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/frolov.py @@ -0,0 +1,42 @@ +import numpy as np +from deterministic_gaussian_sampling_fibonacci import sample_gaussian_fibonacci + +from util.selectors.silder_log import LogSlider +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class CFrolovPWNSampling(CylinderSamplingSchema): + def __init__(self): + def _check_input(val): + return val >= 1 and val <= 100003 and isinstance(val, int) + + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Classical Frolov" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + mu = np.array([mean_x, mean_y]) + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.sample_frolov_gaussian(mu, Cov, sample_count, "ClassicalFrolov") + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/cylinder/partially_wraped_normal/improved_frolov.py b/model/distributions/cylinder/partially_wraped_normal/improved_frolov.py new file mode 100644 index 0000000..e2e15e6 --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/improved_frolov.py @@ -0,0 +1,42 @@ +import numpy as np +from deterministic_gaussian_sampling_fibonacci import sample_gaussian_fibonacci + +from util.selectors.silder_log import LogSlider +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class IFrolovPWNSampling(CylinderSamplingSchema): + def __init__(self): + def _check_input(val): + return val >= 1 and val <= 100003 and isinstance(val, int) + + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Improved Frolov" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + mu = np.array([mean_x, mean_y]) + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.sample_frolov_gaussian(mu, Cov, sample_count, "ImprovedFrolov") + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/cylinder/partially_wraped_normal/partially_warpped_normal.py b/model/distributions/cylinder/partially_wraped_normal/partially_warpped_normal.py new file mode 100644 index 0000000..a4a0d21 --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/partially_warpped_normal.py @@ -0,0 +1,75 @@ +import numpy as np +from scipy.stats import multivariate_normal + +from util.selectors.slider_float import FloatSlider +from util.selectors.slider_pi import PiSlider +from model.distributions.cylinder.cylinder_distribution import CylinderDistribution +from model.distributions.cylinder.partially_wraped_normal.random import CylinderRandomPWNSampling +from model.distributions.cylinder.partially_wraped_normal.fibonacci_rank_1 import CylinderFibRank1PWNSampling +from model.distributions.cylinder.partially_wraped_normal.fibonacci_kronecker import CylinderFibKroneckerPWNSampling +from model.distributions.cylinder.partially_wraped_normal.cartesian import CylinderFibCartPWNSampling +from model.distributions.cylinder.partially_wraped_normal.frolov import CFrolovPWNSampling +from model.distributions.cylinder.partially_wraped_normal.improved_frolov import IFrolovPWNSampling +from model.distributions.cylinder.partially_wraped_normal.fibonacci_frolov import FFrolovPWNSampling + +from model.cylinder.cylinder import Cylinder + +class PartiallyWrappedNormalDistribution(CylinderDistribution): + def __init__(self): + self.distribution_options = [ + PiSlider("Mean x (μₓ)", 0, 1, 2), + PiSlider("Mean y (μᵧ)", 0, 1, 2), + FloatSlider("Sigma x (σₓ)", 0, 0.5, 5.0), + FloatSlider("Sigma y (σᵧ)", 0, 0.5, 1.0), + FloatSlider("Correlation (ρ)", -1, 0.1, 1), + ] + self.sampling_methods = [ + CylinderRandomPWNSampling(), + CylinderFibRank1PWNSampling(), + CylinderFibKroneckerPWNSampling(), + CylinderFibCartPWNSampling(), + CFrolovPWNSampling(), + IFrolovPWNSampling(), + FFrolovPWNSampling(), + ] + + def get_name(self): + return "Partially Wrapped Normal" + + def get_pdf(self, distribution_options): + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_x = distribution_options[2].state + sigma_y = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_x**2, correlation * sigma_x * sigma_y], + [correlation * sigma_x * sigma_y, sigma_y**2] + ]) + + mean = np.array([mean_x, mean_y]) + + dist = multivariate_normal(mean=mean, cov=Cov, allow_singular=True) + + def pdf(x): + alpha = 0.7 # scale + + p, z = Cylinder.xyz_to_p_z(x[:,0], x[:,1], x[:,2]) + p_z = np.column_stack((p, z)) + + # wrap until 3 * sigma_x + k = int(np.ceil(3 * sigma_x / (2 * np.pi))) + + total_pdf = np.zeros(p_z.shape[0]) + for i in range(-k, k+1): + shifted_samples = np.column_stack((p_z[:,0] + i * 2 * np.pi, p_z[:,1])) + total_pdf += dist.pdf(shifted_samples) + + max = np.max(total_pdf) + + norm = total_pdf / max + norm = norm * alpha + return norm + + return pdf diff --git a/model/distributions/cylinder/partially_wraped_normal/random.py b/model/distributions/cylinder/partially_wraped_normal/random.py new file mode 100644 index 0000000..b30c520 --- /dev/null +++ b/model/distributions/cylinder/partially_wraped_normal/random.py @@ -0,0 +1,37 @@ +from abc import ABC, abstractmethod +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_log import LogSlider +import numpy as np +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class CylinderRandomPWNSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_x = distribution_options[2].state + sigma_y = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_x**2, correlation * sigma_x * sigma_y], + [correlation * sigma_x * sigma_y, sigma_y**2] + ]) + + mean = np.array([mean_x, mean_y]) + + samples = np.random.multivariate_normal(mean, Cov, sample_count) + samples[:,0] = samples[:,0] % (2 * np.pi) + + + return samples \ No newline at end of file diff --git a/model/distributions/cylinder/uniform/__init__.py b/model/distributions/cylinder/uniform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/cylinder/uniform/cartesian.py b/model/distributions/cylinder/uniform/cartesian.py new file mode 100644 index 0000000..7735965 --- /dev/null +++ b/model/distributions/cylinder/uniform/cartesian.py @@ -0,0 +1,26 @@ +from abc import ABC, abstractmethod +import numpy as np + +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.slider_square import SliderSquare +from util.cartesian_util import CartesianUtil as cu +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class CylinderCartesianUniformSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 64, 100, 4)) + ] + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + n = int(np.sqrt(sample_count)) + return cu.generate_cartesian_grid(n, (2 * np.pi, 2 * np.pi)) \ No newline at end of file diff --git a/model/distributions/cylinder/uniform/fibonacci_kronecker.py b/model/distributions/cylinder/uniform/fibonacci_kronecker.py new file mode 100644 index 0000000..5839222 --- /dev/null +++ b/model/distributions/cylinder/uniform/fibonacci_kronecker.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_log import LogSlider +import numpy as np +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class CylinderFibUniformSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Fibonacci-Kronecker Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + indices = np.arange(0, sample_count) + gol = (1+5**0.5)/2 + + # centered rank-1 lattice generator + equidistant_generator = (2 * indices + 1) / (2 * sample_count) + + z = equidistant_generator + p = (indices / gol) % 1 + + return np.column_stack((p * 2 * np.pi, z * 2 * np.pi)) \ No newline at end of file diff --git a/model/distributions/cylinder/uniform/fibonacci_rank_1.py b/model/distributions/cylinder/uniform/fibonacci_rank_1.py new file mode 100644 index 0000000..2669e67 --- /dev/null +++ b/model/distributions/cylinder/uniform/fibonacci_rank_1.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.slider_fib import SliderFib +import numpy as np +import sympy as sp +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class CylinderFibRank1UniformSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderFib("Number of Samples", 2, 34, 21, 9)) + ] + + def get_name(self): + return "Fibonacci-Rank-1 Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + k = sample_options[0].idx + + z, p = self.get_rank_1(sample_count, k) + + return np.column_stack((p * 2 * np.pi, z * 2 * np.pi)) + + @staticmethod + def get_rank_1(sample_count, k, without_first_point=False): + indices = np.arange(0, sample_count) + + # centered rank-1 lattice + F_k = int(sp.fibonacci(k - 1)) + F_k_p_1 = sample_count # int(sp.fibonacci(k)) + + z = (indices * (1/F_k_p_1) + (1/(2*F_k_p_1)) ) % 1 + p = (indices * (F_k/F_k_p_1) + (1/(2*F_k_p_1)) ) % 1 + + if without_first_point: + z = z[1:] + p = p[1:] + + return z, p \ No newline at end of file diff --git a/model/distributions/cylinder/uniform/random.py b/model/distributions/cylinder/uniform/random.py new file mode 100644 index 0000000..8d1746d --- /dev/null +++ b/model/distributions/cylinder/uniform/random.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod +from model.distributions.cylinder.cylinder_sampling_schema import CylinderSamplingSchema +from util.selectors.silder_log import LogSlider +import numpy as np +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class CylinderRandomUniformSampling(CylinderSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + t = np.random.uniform(0, 2 * np.pi, sample_count) + p = np.random.uniform(0, 2 * np.pi, sample_count) + + samples = np.column_stack((t, p)) + + return samples \ No newline at end of file diff --git a/model/distributions/cylinder/uniform/uniform.py b/model/distributions/cylinder/uniform/uniform.py new file mode 100644 index 0000000..a1c0eab --- /dev/null +++ b/model/distributions/cylinder/uniform/uniform.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod +from util.selectors.slider import Slider +import numpy as np + +from model.distributions.cylinder.cylinder_distribution import CylinderDistribution +from model.distributions.cylinder.uniform.random import CylinderRandomUniformSampling +from model.distributions.cylinder.uniform.fibonacci_kronecker import CylinderFibUniformSampling +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from model.distributions.cylinder.uniform.cartesian import CylinderCartesianUniformSampling +class UniformCylinderDistribution(CylinderDistribution): + def __init__(self): + self.distribution_options = [] + self.sampling_methods = [ + CylinderRandomUniformSampling(), + CylinderFibUniformSampling(), + CylinderFibRank1UniformSampling(), + CylinderCartesianUniformSampling(), + ] + + def get_name(self): + return "Uniform" + + def get_pdf(self, distribution_options): + scaling_factor = 5 # pdf plotted not to scale + def pdf(x): + N = np.shape(x)[0] + return (1 / (4 * np.pi * np.pi)) * np.ones(N) * scaling_factor + return pdf \ No newline at end of file diff --git a/model/distributions/distribution_loader.py b/model/distributions/distribution_loader.py new file mode 100644 index 0000000..2e86883 --- /dev/null +++ b/model/distributions/distribution_loader.py @@ -0,0 +1,46 @@ +import importlib +import inspect +import pkgutil + + +class DistributionLoader: + """ + Class for loading different probability distributions with a plugin pattern. + + modeled after: https://www.researchgate.net/figure/Class-diagram-for-the-Plugin-Pattern_fig6_221039844 + """ + def __init__(self, type, type_package): + self.distribution_type = type + self.distribution_package = type_package + self.distributions = {} + + self.load_distributions() + + + def load_distributions(self): + pkg = importlib.import_module(self.distribution_package) + if not hasattr(pkg, "__path__"): + raise ValueError(f"'{self.distribution_package}' is not a package (missing __path__).") + + for finder, name, ispkg in pkgutil.walk_packages(pkg.__path__, prefix=pkg.__name__ + "."): + if "benchmark" in name: + continue + try: + module = importlib.import_module(name) + except Exception as e: + print(f"Could not import distribution module '{name}': {e}") + continue + + for _, obj in inspect.getmembers(module, inspect.isclass): + # skip abstract, parametered intervace and non-subclasses + if obj is self.distribution_type: + continue + if not issubclass(obj, self.distribution_type): + continue + if inspect.isabstract(obj): + continue + + self.distributions[obj().get_name()] = obj() + + def get_distributions(self): + return self.distributions diff --git a/model/distributions/gaus1d/__init__.py b/model/distributions/gaus1d/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/gaus1d/gaus1d.py b/model/distributions/gaus1d/gaus1d.py new file mode 100644 index 0000000..3f4af3e --- /dev/null +++ b/model/distributions/gaus1d/gaus1d.py @@ -0,0 +1,183 @@ +import plotly +import dash +from dash import dcc, html, Input, Output, callback, Patch +import plotly.graph_objects as go +import dash_bootstrap_components as dbc +from numpy import sqrt, linspace, vstack, pi, nan, full, exp, square, sort, arange, array +from numpy.random import randn, randint +from scipy.special import erfinv +from pathlib import Path + +from components.popup_box import PopupBox +from model.selfcontained_distribution import SelfContainedDistribution + +class Gaus1D(SelfContainedDistribution): + def __init__(self): + self.methods = ['iid', 'Golden-Sequence', 'Equidistant', 'Unscented'] + + self.config = { + 'toImageButtonOptions': { + 'format': 'svg', # png, svg, pdf, jpeg, webp + 'height': None, # None: use currently-rendered size + 'width': None, + 'filename': 'gauss2d', + }, + 'modeBarButtonsToRemove': ['zoom'], + 'scrollZoom': True, + } + + self.rang = [-5, 5] + self.col_density = plotly.colors.qualitative.Plotly[1] + self.col_samples = plotly.colors.qualitative.Plotly[0] + + + path = Path(__file__).parent / "info_text.md" + with open(path, 'r') as f: + self.info_text = dcc.Markdown(f.read(), mathjax=True) + + self.settings_layout =[ + dbc.Container( + dbc.Col([ + html.P("Select Sampling Method:"), + html.Br(), + + # Sampling Strategy RadioItems + dbc.RadioItems(id='gauss1D-smethod', + options=[{"label": x, "value": x} for x in self.methods], + value=self.methods[randint(len(self.methods))], + inline=True), + + html.Br(), + html.Hr(), + html.Br(), + + # param Slider + dcc.Slider(id="gauss1D-p", min=0, max=1, value=randint(3, 7)/10, + tooltip={"template": "p={value}", "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), + + # L Slider + dcc.Slider(id="gauss1D-L", min=0, max=100, step=1, value=randint(5, 25), + tooltip={"template": "L={value}", "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), + + # μ Slider + dcc.Slider(id="gauss1D-μ", min=-5, max=5, step=0.01, value=randint(-20, 20)/10, + tooltip={"template": "µ={value}", "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), + + # σ Slider + dcc.Slider(id="gauss1D-σ", min=0, max=5, step=0.01, value=randint(5, 20)/10, + tooltip={"template": 'σ={value}', "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), + + html.Hr(), + html.Br(), + + # Info Popup + *PopupBox("gauss1D-info", "Learn More", "Additional Information", self.info_text), + + + ]) + , + fluid=True, + className="g-0" + ) + ] + self.plot_layout = [ + dcc.Graph(id="gauss1D-graph", config=self.config, style={'height': '100%'}), + ] + + self._register_callbacks() + + def _register_callbacks(self): + @callback( + Output('gauss1D-p', 'min'), + Output('gauss1D-p', 'max'), + Output('gauss1D-p', 'value'), + Output('gauss1D-p', 'step'), + Output('gauss1D-p', 'tooltip'), + Output('gauss1D-L', 'disabled'), + Input("gauss1D-smethod", "value"), + ) + def update_smethod(smethod): + patched_tooltip = Patch() + match smethod: + case 'iid': + patched_tooltip.template = "dice" + return 0, 1, .5, 0.001, patched_tooltip, False + case 'Golden-Sequence': + patched_tooltip.template = "z={value}" + return -50, 50, 0, 1, patched_tooltip, False + case 'Equidistant': + patched_tooltip.template = "γ={value}" + return -1, 1, 0, 0.001, patched_tooltip, False + case 'Unscented': + patched_tooltip.template = "{value}" + return 0, 2, 1, 0.001, patched_tooltip, True + case _: + raise Exception("Wrong smethod") + + + @callback( + Output("gauss1D-graph", "figure"), + Input("gauss1D-smethod", "value"), + Input("gauss1D-p", "value"), + Input("gauss1D-L", "value"), + Input("gauss1D-μ", "value"), + Input("gauss1D-σ", "value"), + ) + def update(smethod, p, L, μ, σ): + fig = go.Figure() # TODO use Patch here too + + if σ == 0: + # Dirac Delta + fig.add_trace(go.Scatter(x=[self.rang[0], μ, μ, μ, self.rang[1]], y=[0, 0, 1, 0, 0], hoverinfo='skip', line={'width': 5}, name='Dirac Delta', marker_color=self.col_density, showlegend=True)) + if L > 0: + fig.add_trace(go.Scatter(x=[μ, μ], y=[0, 1], name='Samples', mode='lines', marker_color=self.col_samples, showlegend=True)) + else: + # Draw Samples + xGauss = None + match smethod: + case 'iid': + # xUni = sort(rand(L)) + xGauss = sort(randn(L)*σ + μ) + case 'Golden-Sequence': + xUni = (sqrt(5)-1)/2 * (arange(L)+1+round(p)) % 1 + case 'Equidistant': + xUni = (2*arange(L)+1+p)/(2*L) + case 'Unscented': + # TODO scaled unscented etc + xGauss = array([μ-σ, μ+σ]) # TODO parameter + case _: + raise Exception("Wrong smethod") + # Transform Samples + if xGauss is None: + xGauss = σ*sqrt(2)*erfinv(2*xUni-1) + μ + L2 = len(xGauss) + sample_height = full([1, L2], self.gauss1(0, 0, σ)) + # sample_height = full([1, L], 1/L) + # Plot Density + s = linspace(self.rang[0], self.rang[1], 500) + # https://plotly.com/python-api-reference/generated/plotly.graph_objects.Scatter.html + # TODO lighter fillcolor: fillcolor=matplotlib.colors.to_rgba('#aabbcc80') + fig.add_trace(go.Scatter(x=s, y=self.gauss1(s, μ, σ), hoverinfo='skip', line={'width': 5}, line_shape='spline', name='Density', fill='tozeroy', marker_color=self.col_density, showlegend=True)) + # Plot Samples + xp = vstack((xGauss, xGauss, full([1, L2], nan))).T.flatten() + yp = vstack((full([1, L2], 0), sample_height, full([1, L2], nan))).T.flatten() + fig.add_trace(go.Scatter(x=xp, y=yp, name='Samples', mode='lines', marker_color=self.col_samples, showlegend=True)) + # Style + fig.update_xaxes(range=self.rang, tickmode='array', tickvals=list(range(-5, 6))) + fig.update_yaxes(range=[0, None], fixedrange=True) + fig.update_layout(legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)) + fig.update_layout(dragmode="pan") + + fig.update_layout( + legend=dict( + orientation="v", + xanchor="right", + x=0.1, + ) + ) + return fig + + @staticmethod + def gauss1(x, μ, σ): + return 1/sqrt(2*pi*σ) * exp(-1/2 * square((x-μ)/σ)) + diff --git a/model/distributions/gaus1d/info_text.md b/model/distributions/gaus1d/info_text.md new file mode 100644 index 0000000..8db73ca --- /dev/null +++ b/model/distributions/gaus1d/info_text.md @@ -0,0 +1,43 @@ +## 1D Gaussian +Interactive visualization of the univariate Gaussian density + +$$ +f(x) = \frac{1}{\sqrt{2\pi}\sigma} +\cdot \exp\!\left\{ -\frac{1}{2} \cdot \left(\frac{x-\mu}{\sigma}\right)^2 \right\}\enspace, \quad x\in \mathbb{R} \enspace, +$$ + +with mean $\mu \in \mathbb{R}$ and standard deviation $\sigma \in \mathbb{R}_+$ . + +It was discovered by Karl Friedrich Gauß (1777-1855) in Göttingen, Germany. + +### Formulas +- quantile function +$Q(p) = \mu + \sigma\, \sqrt{2}\, \mathrm{erf}^{-1}(2p-1)$ +- uniform to Gaussian +$x_i^{\text{Gauss}} = Q(x_i^{\text{uni}})$ +- golden Kronecker sequence +$x_i^{\text{uni}}=\mod( \Phi \cdot (i+z), 1) \enspace, \quad i \in \{1,2,\ldots,L\}\enspace, \quad z \in \mathbb{Z}$ +- equidistant samples +$x_i^{\text{uni}} = \frac{2 i - 1 + \gamma}{2 L} \enspace, \quad i \in \{1,2,\ldots,L\}\enspace,\quad \gamma\in[-1,1]$ +- unscented (𝐿=2) +$x_1=\mu-\sigma\enspace, \quad x_2=\mu+\sigma$ +- unscented (𝐿=3) +TODO + +### Interactivity +- GUI +- add/remove lines: click in legend +- sampling methods (radiobutton) +- independent identically distributed (iid), the usual random samples +- golden sequence, a low-discrepancy Kronecker sequence based on the golden ratio +- equidistant, with identical amount of probability mass for all samples +- unscented transform sampling (𝐿=2) +- sampling parameter (slider) +- iid: dice again +- golden: integer offset 𝑧 +- equidistant: offset 𝛾 +- unscented: TODO +- number of Samples 𝐿 (slider) +- density parameters (slider) +- mean 𝜇 +- standard deviation 𝜎 diff --git a/model/distributions/gaus2d/__init__.py b/model/distributions/gaus2d/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/gaus2d/gaus2d.py b/model/distributions/gaus2d/gaus2d.py new file mode 100644 index 0000000..517e6cd --- /dev/null +++ b/model/distributions/gaus2d/gaus2d.py @@ -0,0 +1,378 @@ +from pathlib import Path +import plotly +import dash +import pandas +from urllib.error import HTTPError +from dash import dcc, html, Input, Output, callback, Patch, ctx, no_update, State +import plotly.graph_objects as go +import dash_bootstrap_components as dbc +import numpy as np +from numpy import sqrt, linspace, vstack, hstack, pi, nan, full, exp, square, arange, array, sin, cos, diff, matmul, log10, deg2rad, identity, ones, zeros, diag, cov, mean +from numpy.random import randn, randint +from numpy.linalg import cholesky, eig, det, inv +from scipy.special import erfinv +import deterministic_gaussian_sampling_fibonacci as dgsf + +from components.popup_box import PopupBox +from model.selfcontained_distribution import SelfContainedDistribution + +# Samples Library +# technically, this has state, but its fine because its just a cache +data_dict = {} + +# Get Samples from Library (and load if not available) +def get_data(url): + if not (url in data_dict): + try: + data = pandas.read_csv(url, header=None).to_numpy() + except HTTPError: + # URL doesn't exist + data = full([2, 0], nan) + data_dict[url] = data + return data_dict[url] + +class Gaus2D(SelfContainedDistribution): + def __init__(self): + self.smethods = ['iid', 'Fibonacci', 'LCD', 'SP-Julier04', 'SP-Menegaz11', 'Classical Frolov', 'Improved Frolov', 'Fibonacci Frolov'] # Sampling methods + self.tmethods = ['Cholesky', 'Eigendecomposition'] # Transformation methods + + # Colors + self.col_density = plotly.colors.qualitative.Plotly[1] + self.col_samples = plotly.colors.qualitative.Plotly[0] + + self.config = { + 'toImageButtonOptions': { + 'format': 'jpeg', # png, svg, pdf, jpeg, webp + 'height': None, # None: use currently-rendered size + 'width': None, + 'filename': 'gauss2d', + }, + 'scrollZoom': True, + } + + # axis limits + self.rangx = [-5, 5] + self.rangy = [-4, 4] + + # plot size relative to window size + relwidth = 95 + self.relheight = round((relwidth/diff(self.rangx)*diff(self.rangy))[0]) + + # Gauss ellipse + s = linspace(0, 2*pi, 500) + self.circ = vstack((cos(s), sin(s))) * 2 + + self.fig = go.Figure( + data=[ + go.Scattergl(name='Density', + x=[0], + y=[0], + mode='lines', + marker_color=self.col_density, + showlegend=True, + hoverinfo='skip', + line={'width': 3}, + line_shape='linear', + fill='toself' + ), + go.Scattergl( + name='Samples', + x=[0], + y=[0], + mode='markers', + marker_color=self.col_samples, + marker_line_color='black', + marker_opacity=1, + showlegend=True + ) + ], + ) + self.fig.update_xaxes(range=self.rangx, tickmode='array', tickvals=list(range(self.rangx[0], self.rangx[1]+1))) + self.fig.update_yaxes(range=self.rangy, tickmode='array', tickvals=list(range(self.rangy[0], self.rangy[1]+1)), scaleanchor="x", scaleratio=1) + self.fig.update_layout(legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)) + self.fig.update_layout(modebar_add=['drawopenpath', 'eraseshape'], newshape_line_color='cyan', dragmode='pan') + + self.fig.update_layout( + legend=dict( + orientation="v", + xanchor="right", + x=0.1, + ) + ) + + path = Path(__file__).parent / "info_text.md" + with open(path, 'r') as f: + self.info_text = dcc.Markdown(f.read(), mathjax=True) + + self.settings_layout = [ + dbc.Container( + dbc.Col([ + html.P("Select Sampling Method:"), + html.Br(), + + # Sampling Strategy RadioItems + dbc.RadioItems(id='gauss2D-smethod', + options=[{"label": x, "value": x} for x in self.smethods], + value=self.smethods[randint(len(self.smethods))], + inline=True), + + html.Br(), + + # Transformation Method RadioItems + dbc.RadioItems(id='gauss2D-tmethod', + options=[{"label": x, "value": x} for x in self.tmethods], + value=self.tmethods[randint(len(self.tmethods))], + inline=True), + + html.Br(), + html.Hr(), + html.Br(), + + # param Slider + dcc.Slider(id="gauss2D-p", min=0, max=1, value=randint(3, 7)/10, updatemode='drag', marks=None, + tooltip={"template": "p={value}", "placement": "bottom", "always_visible": True}), + + # L Slider + dcc.Slider(id="gauss2D-L", min=log10(1.2), max=4.001, step=0.001, value=2, updatemode='drag', marks=None, # persistence=True, + tooltip={"template": "L={value}", "placement": "bottom", "always_visible": True, "transform": "trafo_L"}), + + # σ Slider + dcc.Slider(id="gauss2D-σx", min=0, max=5, step=0.01, value=1, updatemode='drag', marks=None, + tooltip={"template": 'σx={value}', "placement": "bottom", "always_visible": True}), + dcc.Slider(id="gauss2D-σy", min=0, max=5, step=0.01, value=1, updatemode='drag', marks=None, + tooltip={"template": 'σy={value}', "placement": "bottom", "always_visible": True}), + + # ρ Slider + dcc.Slider(id="gauss2D-ρ", min=-1, max=1, step=0.001, value=0, updatemode='drag', marks=None, + tooltip={"template": 'ρ={value}', "placement": "bottom", "always_visible": True}), + + # ρ Slider + dcc.Slider(id="angle", min=0, max=180, step=2, value=0, updatemode='drag', marks=None, + tooltip={"template": 'angle={value}°', "placement": "bottom", "always_visible": True}), + + html.Hr(), + html.Br(), + + + # Info Popup + *PopupBox("gauss2D-info", "Learn More", "Additional Information", self.info_text), + ]), + fluid=True, + className="g-0") + ] + + self.plot_layout = [ + dcc.Graph(id="gauss2D-graph", figure=self.fig, config=self.config, style={'height': '100%'}), + ] + + self._register_callbacks() + + def _register_callbacks(self): + @callback( + Output('gauss2D-p', 'min'), + Output('gauss2D-p', 'max'), + Output('gauss2D-p', 'value'), + Output('gauss2D-p', 'step'), + Output('gauss2D-p', 'tooltip'), + Output('gauss2D-L', 'disabled'), + Output('gauss2D-p', 'className'), # to hide when not needed + Input("gauss2D-smethod", "value"), + ) + def update_smethod(smethod): + patched_tooltip = Patch() + patched_tooltip.always_visible = True + match smethod: + case 'iid': + patched_tooltip.template = "dice" + # min, max, value, step, tooltip + return 0, 1, .5, 0.001, patched_tooltip, False, 'visible' + case 'Fibonacci': + patched_tooltip.template = "z={value}" + return -50, 50, 0, 1, patched_tooltip, False, 'visible' + case 'LCD': + patched_tooltip.template = "α={value}°" + return -360, 360, 0, 0.1, patched_tooltip, False, 'visible' + case 'SP-Julier04': + patched_tooltip.template = "W₀={value}" + return -2, 1, .1, 0.001, patched_tooltip, True, 'visible' + case 'SP-Menegaz11': + patched_tooltip.template = "Wₙ₊₁={value}" + return 0, 1, 1/3, 0.001, patched_tooltip, True, 'visible' + case 'Classical Frolov': + patched_tooltip.always_visible = False + return no_update, no_update, no_update, no_update, patched_tooltip, False, 'invisible' + case 'Improved Frolov': + patched_tooltip.always_visible = False + return no_update, no_update, no_update, no_update, patched_tooltip, False, 'invisible' + case 'Fibonacci Frolov': + patched_tooltip.always_visible = False + return no_update, no_update, no_update, no_update, patched_tooltip, False, 'invisible' + case _: + raise Exception("Wrong smethod") + + + @callback( + Output("gauss2D-graph", "figure"), + Output("gauss2D-σx", "value"), + Output("gauss2D-σy", "value"), + Output("gauss2D-ρ", "value"), + Output("angle", "value"), + Input("gauss2D-smethod", "value"), + Input("gauss2D-tmethod", "value"), + Input("gauss2D-p", "value"), + Input("gauss2D-L", "value"), + Input("gauss2D-σx", "value"), + Input("gauss2D-σy", "value"), + Input("gauss2D-ρ", "value"), + Input("angle", "value"), + ) + def update(smethod, tmethod, p, L0, sigma_x, sigma_y, rho, angle): + trig = ctx.triggered_id + + def _rot2d(angle_deg): + a = np.deg2rad(angle_deg) + c, s = np.cos(a), np.sin(a) + return np.array([[c, -s], + [s, c]], dtype=float) + + + + + if trig == "angle": + R = _rot2d(angle) + C = np.array([[sigma_x**2, sigma_x*sigma_y*rho], + [sigma_x*sigma_y*rho, sigma_y**2]]) + w, _ = np.linalg.eigh(C) + w = np.sort(w)[::-1] + C_rot = R @ np.diag(w) @ R.T + sigma_x = np.sqrt(C_rot[0, 0]) + sigma_y = np.sqrt(C_rot[1, 1]) + if sigma_x > 0: + rho = C_rot[0, 1] / (sigma_x * sigma_y) + else: + rho = 0.0 + silder_changes = (sigma_x, sigma_y, rho, no_update) + else: + if np.isclose(rho, 0) and np.isclose(sigma_x, sigma_y): + angle = 0.0 + else: + angle = 0.5 * np.arctan2(2 * rho * sigma_x * sigma_y, sigma_x**2 - sigma_y**2) + angle = np.rad2deg(angle) + angle = angle % 180 + silder_changes = (no_update, no_update, no_update, angle) + + + # Slider Transform, + L = self.trafo_L(L0) + # Mean + # μ = array([[μx], [μy]]) + μ = array([[0], [0]]) + # Covariance + C = array([[square(sigma_x), sigma_x*sigma_y*rho], [sigma_x*sigma_y*rho, square(sigma_y)]]) + C_D, C_R = eig(C) + C_D = C_D[..., None] # to column vector + + patched_fig = Patch() + # Draw SND + weights = None + match smethod: + case 'iid': + xySND = randn(2, L) + case 'Fibonacci': + # TODO 2nd parameter + xUni = (sqrt(5)-1)/2 * (arange(L)+1+round(p)) % 1 + yUni = (2*arange(L)+1)/(2*L) # +p + xyUni = vstack((xUni, yUni)) + xySND = sqrt(2)*erfinv(2*xyUni-1) + case 'LCD': + xySND = get_data(self.url_SND_LCD(2, L)) + xySND = matmul(self.rot(p), xySND) + case 'SP-Julier04': + # https://ieeexplore.ieee.org/abstract/document/1271397 + Nx = 2 # dimension + x0 = zeros([Nx, 1]) + W0 = full([1, 1], p) # parameter, W0<1 + x1 = sqrt(Nx/(1-W0) * identity(Nx)) + W1 = full([1, Nx], (1-W0)/(2*Nx)) + x2 = -x1 + W2 = W1 + xySND = hstack((x0, x1, x2)) + weights = hstack((W0, W1, W2)) + case 'SP-Menegaz11': + # https://ieeexplore.ieee.org/abstract/document/6161480 + n = 2 # dimension + w0 = p # parameter, 0 0: + grid = dgsf.get_uniform_grid(2, L, method) + else: + grid = np.empty((L, 2)) + xyUni = grid.T + xySND = sqrt(2)*erfinv(2*xyUni-1) + case _: + raise Exception("Wrong smethod") + match tmethod: + case 'Cholesky': + xyG = matmul(cholesky(C), xySND) + μ + case 'Eigendecomposition': + xyG = matmul(C_R, sqrt(C_D) * xySND) + μ + case _: + raise Exception("Wrong smethod") + # Sample weights to scatter sizes + L2 = xySND.shape[1] # actual number of saamples + if L2 == 0: + sizes = 10 + else: + if weights is None: + weights = 1/L2 # equally weighted + else: + weights = weights.flatten() + sizes = sqrt(abs(weights) * L2) * det(2*pi*C)**(1/4) / sqrt(L2) * 70 + # Plot Ellipse + elp = matmul(C_R, sqrt(C_D) * self.circ) + μ + patched_fig['data'][0]['x'] = elp[0, :] + patched_fig['data'][0]['y'] = elp[1, :] + # Plot Samples + patched_fig['data'][1]['x'] = xyG[0, :] + patched_fig['data'][1]['y'] = xyG[1, :] + patched_fig['data'][1]['marker']['size'] = sizes + patched_fig['data'][1]['marker']['line']['width'] = sizes/20 + return patched_fig, *silder_changes + + + @staticmethod + def url_SND_LCD(D, L): + return f'https://raw.githubusercontent.com/KIT-ISAS/deterministic-samples-csv/main/standard-normal/glcd/D{D}-N{L}.csv' + + @staticmethod + def gauss1(x, μ, σ): + return 1/sqrt(2*pi*σ) * exp(-1/2 * square((x-μ)/σ)) + + + # Slider Transform, must be idencital to window.dccFunctions.trafo_L in assets/tooltip.js + @staticmethod + def trafo_L(L0): + if L0 < log10(1.25): + return 0 + else: + return round(10 ** L0) + + @staticmethod + def rot(a): + ar = deg2rad(a) + return array([[cos(ar), -sin(ar)], [sin(ar), cos(ar)]]) + + + + diff --git a/model/distributions/gaus2d/info_text.md b/model/distributions/gaus2d/info_text.md new file mode 100644 index 0000000..b20c99a --- /dev/null +++ b/model/distributions/gaus2d/info_text.md @@ -0,0 +1,71 @@ +## 2D Gaussian +Interactive visualizaton of the bivariate Gaussian density + +$$ +f(\underline x) = \mathcal{N}(\underline x; \underline \mu, \textbf{C}) = +\frac{1}{2\pi \sqrt{\det(\textbf{C})}} +\cdot \exp\!\left\{ -\frac{1}{2} +\cdot (\underline x - \underline \mu)^\top \textbf{C}^{-1} (\underline x - \underline \mu) \right\} \enspace, +\quad \underline{x}\in \mathbb{R}^2 \enspace, \quad \textbf{C} \enspace \text{positive semidefinite} \enspace. +$$ + +### Formulas and Literature +- quantile function + $Q(p) = \sqrt{2}\, \mathrm{erf}^{-1}(2p-1)$ +- uniform to SND + $\underline x_i^{\text{SND}} = Q(\underline x_i^{\text{uni}})$ +- SND to Gauss: Cholesky + $\underline x_i^{\text{Gauss}} = \mathrm{chol}(\textbf{C}) \cdot \underline x_i^{\text{SND}}$ +- SND to Gauss: Eigendecomposition + [[Frisch23](https://isif.org/media/generalized-fibonacci-grid-low-discrepancy-point-set-optimal-deterministic-gaussian), eq. 18], + [[Frisch21](https://ieeexplore.ieee.org/document/9626975), eq. 4] + $\underline x_i^{\text{Gauss}} = \mathbf{V} \cdot \sqrt{\mathbf{D}} \cdot \underline x_i^{\text{SND}}$ +- Fibonacci-Kronecker Lattice + $\underline x_i^{\text{uni}} = \begin{bmatrix}\mod( \Phi \cdot (i+z), 1) \\ \frac{2 i - 1 + \gamma}{2 L} \end{bmatrix} \enspace, + \quad i \in \{1,2,\ldots,L\}\enspace, \quad z \in \mathbb{Z} \enspace, \quad \gamma\in[-1,1]$ +- LCD: Localized Cumulative Distribution + [[Hanebeck08](https://ieeexplore.ieee.org/document/4648104)], + [[Hanebeck09](https://ieeexplore.ieee.org/document/5400649)], + loaded from [library](https://github.com/KIT-ISAS/deterministic-samples-csv) + $K(\underline x - \underline m, b) = \exp\!\left\{ -\frac{1}{2} \cdot \left\Vert \frac{\underline x - \underline m}{b} \right\Vert_2^2 \right\} \enspace, \quad + F(\underline m, b) = \int_{\mathbb{R}^2} f(\underline x) \, K(\underline x - \underline m, b) \, \mathrm{d} \underline x \enspace,$ + $\widetilde f(x) = \mathcal{N}(\underline x; \underline 0, \textbf{I}) \enspace, \quad + f(\underline x) = \sum_{i=1}^L \delta(\underline x - \underline x_i) \enspace,$ + $D = \int_{\mathbb{R}_+} w(b) \int_{\mathbb{R}^2} \left( \widetilde F(\underline m, b) - F(\underline m, b) \right)^2 \mathrm{d} \underline m \, \mathrm{d} b \enspace,$ + $\left\{\underline x_i^{\text{SND}}\right\}_{i=0}^L = \arg \min_{\underline x_i} \{D\}$ +- SP-Julier14: Sigma Points + [[Julier04](https://ieeexplore.ieee.org/document/1271397), eq. 12] + $L=2\cdot d + 1 \enspace, \quad i\in\{1,2,\dots,d\} \enspace,$ + $\underline x_0=\underline 0 \enspace, \quad W_0 < 1 \enspace,$ + $\underline x_i = \sqrt{\frac{L}{1-W_0}} \cdot \underline e_i \enspace, \quad W_i = \frac{1-W_0}{2 L} \enspace,$ + $\underline x_{i+L} = -x_i \enspace, \quad W_{i+L} = W_i$ +- SP-Menegaz11: Minimum Sigma Set + [[Menegaz11](https://ieeexplore.ieee.org/abstract/document/6161480), eq. 2-8] + $L=d+1 \enspace, \quad i \in \{1,2, \dots d\} \enspace,$ + $0 < w_0 < 1 \enspace, \quad + \alpha=\sqrt{\frac{1-w_0}{d}} \enspace, \quad + C = \sqrt{\mathbf{I} - \alpha^2 \cdot \mathbf 1} \enspace, \quad + \underline x_0 = - \frac{\alpha}{\sqrt{w_0}} \cdot \underline 1$ + $w_{1\colon n} = \mathrm{diag}(w_0 \cdot \alpha^2 \cdot C^{-1} \cdot \mathbf{1} \cdot (C^\top)^{-1}) \enspace,$ + $\underline x_{1\colon n} = C \cdot (\sqrt{\mathbf{I} \cdot w_{1\colon n}})^{-1}$ +### Interactivity +- GUI + - add/remove lines: click in legend +- sampling methods (radiobutton) + - Independent identically distributed (iid), the usual random samples. + - Fibonacci-Kronecker lattice, combination of 1D golden sequence and equidistant. Use with eigendecomposition for best homogeneity. + - LCD SND samples. + - Sigma Points - Julier04. +- transformation methods (radiobutton) + - Cholesky decomposition. + - Eigenvalue-Eigenvector decomposition. +- sampling parameter (slider) + - iid: dice again + - Fibonacci: integer offset 𝑧, offset 𝛾 + - LCD: SND rotation 𝛼, a proxy for dependency on initial guess during optimization + - sigma points: scaling parameter +- number of Samples 𝐿 (slider) +- density parameters (slider) + - standard deviation $\sigma_x$ + - standard deviation $\sigma_y$ + - correlation coefficient $\rho$ \ No newline at end of file diff --git a/model/distributions/sphere/__init__.py b/model/distributions/sphere/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/sphere/bingham/__init__.py b/model/distributions/sphere/bingham/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/sphere/bingham/bingham.py b/model/distributions/sphere/bingham/bingham.py new file mode 100644 index 0000000..c5892a6 --- /dev/null +++ b/model/distributions/sphere/bingham/bingham.py @@ -0,0 +1,45 @@ +from abc import ABC, abstractmethod +from model.distributions.sphere.sphere_distribution import SphereDistribution +from util.selectors.slider import Slider +import numpy as np + +import pyrecest._backend +from pyrecest.backend import array +from pyrecest.distributions import BinghamDistribution + +from model.distributions.sphere.bingham.random import BinghamRandomSampling + + +class BinghampDistribution(SphereDistribution): + def __init__(self): + self.distribution_options = [ + Slider("Lambda 1 (λ₁)", 0, 0, 10), + Slider("Lambda 2 (λ₂)", 0, 0, 10), + ] + + self.sampling_methods = [ + BinghamRandomSampling() + ] + + def get_name(self): + return "Bingham" + + def get_pdf(self, distribution_options): + alpha = 0.7 # scale + + l1 = distribution_options[0].state + l2 = distribution_options[1].state + + lambdas = np.sort(np.array([l1, l2, 0]))[::-1] + lambdas = -lambdas + M = np.eye(3) + bingham_dist = BinghamDistribution(M=array(M), Z=array(lambdas)) + + + def pdf(x): + bing = bingham_dist.pdf(array(x)) + max = np.max(bing) + norm = bing / max + norm = norm * alpha + return norm + return pdf \ No newline at end of file diff --git a/model/distributions/sphere/bingham/random.py b/model/distributions/sphere/bingham/random.py new file mode 100644 index 0000000..86c65b1 --- /dev/null +++ b/model/distributions/sphere/bingham/random.py @@ -0,0 +1,30 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy +import sphstat + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class BinghamRandomSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + l1 = distribution_options[0].state + l2 = distribution_options[1].state + + lambdas = np.sort(np.array([l1, l2]))[::-1] + + numsamp = sample_options[0].state + samples = sphstat.distributions.bingham(numsamp, lambdas)["points"] + samples_array = np.vstack(samples) + + return samples_array \ No newline at end of file diff --git a/model/distributions/sphere/kent/__init__.py b/model/distributions/sphere/kent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/sphere/kent/kent.py b/model/distributions/sphere/kent/kent.py new file mode 100644 index 0000000..4a0fe26 --- /dev/null +++ b/model/distributions/sphere/kent/kent.py @@ -0,0 +1,52 @@ +from abc import ABC, abstractmethod +from model.distributions.sphere.sphere_distribution import SphereDistribution +from model.sphere.sphere import Sphere +from util.selectors.slider import Slider +from util.selectors.slider_float import FloatSlider +import numpy as np + +from model.distributions.sphere.kent.random import KentRandomSampling +from sphstat.descriptives import rotationmatrix_withaxis + +class KentDistribution(SphereDistribution): + def __init__(self): + self.distribution_options = [ + Slider("κ (kappa)", 0.0, 10.0, 50.0), + Slider("β (beta)", 0.0, 2.0, 25.0), + ] + + self.sampling_methods = [ + KentRandomSampling() + ] + + def get_name(self): + return "Kent (5-parameter Fisher-Bingham - FB5)" + + def get_pdf(self, distribution_options): + # we don't compute the normalization constant $c(\kappa, \beta)$ here + # because the density fucntions are plotted not to scale anyway + c = 1.0 + + kappa = distribution_options[0].state + beta = distribution_options[1].state + beta = min(beta, kappa / 2) + + # hardcoded, same as in kent random sampling + y3 = np.array([1, 0, 0]).reshape(3, 1) + y2 = np.array([0, 1, 0]).reshape(3, 1) + y1 = np.array([0, 0, 1]).reshape(3, 1) + + + + def pdf(x): + #print(f"{y1.T.shape} * {x.T.shape}") + kent = 1/c * np.exp( + kappa * (y1.T @ x.T) + + beta * ( (y2.T @ x.T)**2 - (y3.T @ x.T)**2 ) + ) + kent = kent.flatten() + max = np.max(kent) + norm = kent / max + return norm + return pdf + \ No newline at end of file diff --git a/model/distributions/sphere/kent/random.py b/model/distributions/sphere/kent/random.py new file mode 100644 index 0000000..0f8ce76 --- /dev/null +++ b/model/distributions/sphere/kent/random.py @@ -0,0 +1,31 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy +import sphstat +from kent_distribution import kent2 + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + + +class KentRandomSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)), + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + kappa = distribution_options[0].state + beta = distribution_options[1].state + beta = min(beta, kappa / 2) # TODO make this dynamic + + kent = kent2([1, 0, 0], [0, 1, 0], [0, 0, 1], kappa, beta) + numsamp = sample_options[0].state + samp = kent.rvs(n_samples=numsamp) + xyz = samp[:, [2, 1, 0]] # samples get returned in z,y,x order + return xyz \ No newline at end of file diff --git a/model/distributions/sphere/sphere_distribution.py b/model/distributions/sphere/sphere_distribution.py new file mode 100644 index 0000000..d782611 --- /dev/null +++ b/model/distributions/sphere/sphere_distribution.py @@ -0,0 +1,26 @@ +from abc import ABC, abstractmethod +from functools import cached_property +from model.distribution import Distribution + + +class SphereDistribution(Distribution): + def __init__(self): + self.distribution_options = [] + # a list of objects that implement the torus_sampling_schema interface + # that can be used with this distribution + self.sampling_methods = [] + + + # Returns the name of the distribution + @abstractmethod + def get_name(self): + pass + + @cached_property + def sampling_method_dict(self): + return {m.get_name(): m for m in self.sampling_methods} + + # returns a functions that takes a (N, 3) shape nparray and returns the pdf value at the points + @abstractmethod + def get_pdf(self, distribution_options): + pass \ No newline at end of file diff --git a/model/distributions/sphere/sphere_sampling_schema.py b/model/distributions/sphere/sphere_sampling_schema.py new file mode 100644 index 0000000..504f896 --- /dev/null +++ b/model/distributions/sphere/sphere_sampling_schema.py @@ -0,0 +1,17 @@ +from abc import ABC, abstractmethod +from model.sampling_schema import SamplingSchema + +class SphereSamplingSchema(SamplingSchema): + def __init__(self): + self.sample_options = [] + + + # Returns the name of the sampling method + @abstractmethod + def get_name(self): + pass + + # returns samples as a numpy array of shape (n, 3) + @abstractmethod + def sample(self, sample_options, distribution_options): + pass \ No newline at end of file diff --git a/model/distributions/sphere/uniform/__init__.py b/model/distributions/sphere/uniform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/sphere/uniform/cartesian.py b/model/distributions/sphere/uniform/cartesian.py new file mode 100644 index 0000000..bd71453 --- /dev/null +++ b/model/distributions/sphere/uniform/cartesian.py @@ -0,0 +1,28 @@ +import numpy as np + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.slider_square import SliderSquare +from util.cartesian_util import CartesianUtil as cu +from model.sphere.sphere import Sphere +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class SphereCartesianUniformSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 64, 100, 4)) + ] + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + n = int(np.sqrt(sample_count)) + grid_spherical = cu.generate_cartesian_grid(n, (np.pi, 2 * np.pi)) + x, y, z = Sphere.spherical_to_cartesian(grid_spherical[:,0], grid_spherical[:,1]) + return np.column_stack((x, y, z)) \ No newline at end of file diff --git a/model/distributions/sphere/uniform/fibonacci_rank_1.py b/model/distributions/sphere/uniform/fibonacci_rank_1.py new file mode 100644 index 0000000..0515491 --- /dev/null +++ b/model/distributions/sphere/uniform/fibonacci_rank_1.py @@ -0,0 +1,32 @@ +from abc import ABC, abstractmethod +import numpy as np + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_fib import SliderFib +from model.sphere.sphere import Sphere +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class SphereFibRank1UniformSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderFib("Number of Samples", 2, 34, 21, 9)) + ] + self.sampler = CylinderFibRank1UniformSampling() + + def get_name(self): + return "Fibonacci-Rank-1 Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + t, p = self.sampler.get_rank_1(sample_count, sample_options[0].idx) + + z = 1 - 2 * t # uniform in [-1, 1] + phi = p * (2 * np.pi) + x = np.sqrt(1 - z**2) * np.cos(phi) + y = np.sqrt(1 - z**2) * np.sin(phi) + + + return np.column_stack((x, y, z)) \ No newline at end of file diff --git a/model/distributions/sphere/uniform/fibonachi_lattice.py b/model/distributions/sphere/uniform/fibonachi_lattice.py new file mode 100644 index 0000000..558526d --- /dev/null +++ b/model/distributions/sphere/uniform/fibonachi_lattice.py @@ -0,0 +1,42 @@ +from abc import ABC, abstractmethod +import numpy as np + + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class SphereUniformFibSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Fibonacci-Kronecker Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + gold_seq = (1+5**0.5)/2 # golden ratio + + indices = np.arange(0, sample_count) + + # centered rank-1 lattice generator + # see https://isas.iar.kit.edu/pdf/SDFMFI23_Frisch.pdf Forumla 2 + equidistant_generator = (2 * indices + 1) / (2 * sample_count) + w = equidistant_generator + + # w is gererated in [0, 1], we need it in [-1, 1] + w = 1 - 2 * w + + # map to sphere + # based on https://isas.iar.kit.edu/pdf/SDFMFI23_Frisch.pdf, Forumla 39 and 40 + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( (2 * np.pi * indices) / gold_seq) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( (2 * np.pi * indices) / gold_seq) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) + + + return x_i_f \ No newline at end of file diff --git a/model/distributions/sphere/uniform/random.py b/model/distributions/sphere/uniform/random.py new file mode 100644 index 0000000..4735ac9 --- /dev/null +++ b/model/distributions/sphere/uniform/random.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +import numpy as np + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class SphereUniformRandomSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + samples = np.random.normal(size=(sample_count, 3)) + samples /= np.linalg.norm(samples, axis=1)[:, np.newaxis] + + return samples \ No newline at end of file diff --git a/model/distributions/sphere/uniform/uniform.py b/model/distributions/sphere/uniform/uniform.py new file mode 100644 index 0000000..f44930c --- /dev/null +++ b/model/distributions/sphere/uniform/uniform.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +import numpy as np + +from model.distributions.sphere.sphere_distribution import SphereDistribution +from model.distributions.sphere.uniform.random import SphereUniformRandomSampling +from model.distributions.sphere.uniform.fibonachi_lattice import SphereUniformFibSampling +from model.distributions.sphere.uniform.fibonacci_rank_1 import SphereFibRank1UniformSampling +from model.distributions.sphere.uniform.cartesian import SphereCartesianUniformSampling + +class SphereUniformDistribution(SphereDistribution): + def __init__(self): + self.distribution_options = [] + self.sampling_methods = [ + SphereUniformRandomSampling(), + SphereUniformFibSampling(), + SphereFibRank1UniformSampling(), + SphereCartesianUniformSampling(), + ] + + + def get_name(self): + return "Uniform" + + def get_pdf(self, distribution_options): + # https://math.stackexchange.com/questions/2315341/how-to-write-a-proper-definition-of-the-uniform-distribution-on-unit-sphere + def pdf(x): + N = np.shape(x)[0] + return (1/ (4*np.pi)) * np.ones(N) + return pdf \ No newline at end of file diff --git a/model/distributions/sphere/vonmises_fisher/__init__.py b/model/distributions/sphere/vonmises_fisher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/sphere/vonmises_fisher/cartesian.py b/model/distributions/sphere/vonmises_fisher/cartesian.py new file mode 100644 index 0000000..3dfd91c --- /dev/null +++ b/model/distributions/sphere/vonmises_fisher/cartesian.py @@ -0,0 +1,41 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.slider_square import SliderSquare +from util.cartesian_util import CartesianUtil as cu +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class VonMisesCartesianSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 64, 100, 4)) + ] + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + n = int(np.sqrt(sample_count)) + k = distribution_options[0].state # kappa + + grid = cu.generate_cartesian_grid(n, (1, 1)) + x, y = grid[:,0], grid[:,1] + phi = 2 * np.pi * y # azimuthal angle, [0, 2pi] uniform + + w = 1 + (1/k) * np.log1p(x * np.expm1(-2 * k)) + w = np.clip(w, -1.0, 1.0) # clamp to avoid sqrt warnings due to numerical issues + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( phi) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( phi) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + return x_i_f + + diff --git a/model/distributions/sphere/vonmises_fisher/fibonachi.py b/model/distributions/sphere/vonmises_fisher/fibonachi.py new file mode 100644 index 0000000..6c63da0 --- /dev/null +++ b/model/distributions/sphere/vonmises_fisher/fibonachi.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class VonMisesFibSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)), + ] + + def get_name(self): + return "Fibonacci Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + k = distribution_options[0].state # kappa + + gold_seq = (1+5**0.5)/2 # golden ratio + + indices = np.arange(0, sample_count) + + w = 1 + (1/k) * np.log1p((2*indices -1)/ (2*sample_count ) * np.expm1(-2 * k)) + w = np.clip(w, -1.0, 1.0) # clamp to avoid sqrt warnings due to numerical issues + + # based on https://isas.iar.kit.edu/pdf/SDFMFI23_Frisch.pdf, Forumla 39 and 40 + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( (2 * np.pi * indices) / gold_seq) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( (2 * np.pi * indices) / gold_seq) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + + return x_i_f \ No newline at end of file diff --git a/model/distributions/sphere/vonmises_fisher/random.py b/model/distributions/sphere/vonmises_fisher/random.py new file mode 100644 index 0000000..3910dd7 --- /dev/null +++ b/model/distributions/sphere/vonmises_fisher/random.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class VonMisesRandomSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)), + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + kappa = distribution_options[0].state + + samples = scipy.stats.vonmises_fisher.rvs(mu=[0,0,1], kappa=kappa, size=sample_count) + + return samples \ No newline at end of file diff --git a/model/distributions/sphere/vonmises_fisher/vonmises_fisher.py b/model/distributions/sphere/vonmises_fisher/vonmises_fisher.py new file mode 100644 index 0000000..6af0b7e --- /dev/null +++ b/model/distributions/sphere/vonmises_fisher/vonmises_fisher.py @@ -0,0 +1,38 @@ +from abc import ABC, abstractmethod +from model.distributions.sphere.sphere_distribution import SphereDistribution +from util.selectors.silder_log import LogSlider +import numpy as np +import scipy + +from model.distributions.sphere.vonmises_fisher.random import VonMisesRandomSampling +from model.distributions.sphere.vonmises_fisher.fibonachi import VonMisesFibSampling +from model.distributions.sphere.vonmises_fisher.cartesian import VonMisesCartesianSampling +class vonMisesFisherDistribution(SphereDistribution): + def __init__(self): + self.distribution_options = [ + LogSlider("Kappa (κ)", 1, 5, 100), + ] + + self.sampling_methods = [ + VonMisesRandomSampling(), + VonMisesFibSampling(), + VonMisesCartesianSampling(), + ] + + def get_name(self): + return "von Mises-Fisher" + + def get_pdf(self, distribution_options): + alpha = 0.7 # scale + kappa = distribution_options[0].state + + def pdf(x): + + misf = scipy.stats.vonmises_fisher.pdf(x, mu=[0,0,1], kappa=kappa) + max = np.max(misf) + + norm = misf / max + norm = norm * alpha + return norm + + return pdf \ No newline at end of file diff --git a/model/distributions/sphere/watson/__init__.py b/model/distributions/sphere/watson/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/sphere/watson/benchmark_fib_starts.py b/model/distributions/sphere/watson/benchmark_fib_starts.py new file mode 100644 index 0000000..c815664 --- /dev/null +++ b/model/distributions/sphere/watson/benchmark_fib_starts.py @@ -0,0 +1,219 @@ +''' +Run this file directly from project root with: + +sudo "$(poetry run which python)" -m pyperf system tune +PYTHONPATH=$PWD poetry run python model/distributions/sphere/watson/benchmark_fib_starts.py + + +you can also plot from existing JSON files by setting the environment variable: +PLOT_FROM_JSON=1 PYTHONPATH=$PWD poetry run python model/distributions/sphere/watson/benchmark_fib_starts.py +''' + +import json +import os +from pathlib import Path +import plotly.express as px +from model.distributions.sphere.watson.fibonachi import WatsonFibonachiSampling +from util.selectors.slider_float import FloatSlider +import pyperf +import statistics +import numpy as np + +sampler = WatsonFibonachiSampling() +methods = { + "Closed-Form" : sampler.sample_closed, + "Inverse Interpolation" : sampler.sample_inverse_interpolation, + "Inverse ODE" : sampler.sample_inverse_ode, + "ODE Event Locations" : sampler.sample_events, +} + + +def benchmark_kappa(method, kappa, sample_count, times=50): + distribution_options = [FloatSlider("", 0, kappa, 100)] + sampling_options = [FloatSlider("", 0, sample_count, sample_count)] + for time in range(times): + method(sampling_options, distribution_options) + +def bench_single_kappa(kappa, sample_count, id): + results = {} + for method_name, method in methods.items(): + bench_name = f"Watson Fibonacci Sampling: {method_name} (kappa={kappa}) [{id}]" + benchmark = runner.bench_func(bench_name, benchmark_kappa, method, kappa, sample_count, 1) + results[method_name] = benchmark + + return results + +def bench_multiple_kappa(): + sample_count = 10000 + all_results = {} + for kappa in range(-30, 31, 2): + res = bench_single_kappa(kappa, sample_count, "Multiple Kappa") + for name, bench in res.items(): + if name not in all_results: + all_results[name] = [] + all_results[name].append((kappa, bench)) + return all_results + +def bench_multiple_sample_counts(kappa): + all_results = {} + for sample_count in range(100, 1001, 10): + res = bench_single_kappa(kappa, sample_count, f"Multiple Sample Counts (kappa={kappa}, sample_count={sample_count})") + for name, bench in res.items(): + if name not in all_results: + all_results[name] = [] + all_results[name].append((sample_count, bench)) + return all_results + +def bench_multiple_sample_counts_log(kappa): + all_results = {} + sample_counts = np.unique(np.logspace(2, 5, num=90, dtype=int)) + for sample_count in sample_counts: + res = bench_single_kappa(kappa, sample_count, f"Multiple Sample Counts Log (kappa={kappa}, sample_count={sample_count})") + for name, bench in res.items(): + if name not in all_results: + all_results[name] = [] + all_results[name].append((sample_count, bench)) + return all_results + + +def _sanitize_filename(name): + return name.replace(" ", "_").replace(":", "") + +def _rows_from_results(results, x_label): + def _to_builtin(value): + if isinstance(value, np.integer): + return int(value) + if isinstance(value, np.floating): + return float(value) + return value + + if x_label == "sample_count": + return [ + dict(name=n, sample_count=_to_builtin(k), time=_to_builtin(t.mean())) + for n, pts in results.items() + for k, t in pts + ] + if x_label == "kappa": + return [ + dict(name=n, kappa=_to_builtin(k), time=_to_builtin(t.mean())) + for n, pts in results.items() + for k, t in pts + ] + raise ValueError(f"Unsupported x_label: {x_label}") + +def _plot_rows(rows, title, filename, x_label, log_x=False, log_y=False): + fig = px.line( + rows, + x=x_label, + y="time", + color="name", + markers=True, + log_x=log_x, + log_y=log_y, + labels={"time": "time in s", "sample_count": "sample count"}, + ) + fig.update_layout( + legend=dict( + orientation="h", + yanchor="bottom", + y=1.02, + xanchor="left", + x=0, + ), + font=dict(size=26), + ) + if log_x: + fig.update_xaxes(dtick=1) + if log_y: + fig.update_yaxes(dtick=1) + fig.update_layout(legend_title_text="") + + try: + fig.write_image(f"{_sanitize_filename(filename)}.svg") + except Exception as e: + print("Generating plot failed, dumping data:", e) + print(rows) + print("Trying to save html as fallback") + fig.write_html(f"{_sanitize_filename(title)}.html", include_plotlyjs="cdn", full_html=True) + +def plot_benches(results, title=None, filename=None, x_label=None, log_x=None, log_y=None, json_filename=None): + + if isinstance(results, (str, Path)): + json_path = Path(results) + with json_path.open("r", encoding="utf-8") as handle: + payload = json.load(handle) + rows = payload["rows"] + if title is None: + title = payload.get("title", json_path.stem) + if filename is None: + filename = payload.get("filename", json_path.stem) + if x_label is None: + x_label = payload.get("x_label") + if log_x is None: + log_x = payload.get("log_x", False) + if log_y is None: + log_y = payload.get("log_y", False) + if x_label is None: + raise ValueError("x_label is required when replotting from JSON") + _plot_rows(rows, title, filename, x_label, log_x=log_x, log_y=log_y) + return + + if title is None or filename is None or x_label is None: + raise ValueError("title, filename, and x_label are required for raw benchmark data") + if log_x is None: + log_x = False + if log_y is None: + log_y = False + + rows = _rows_from_results(results, x_label) + payload = { + "title": title, + "filename": filename, + "x_label": x_label, + "log_x": log_x, + "log_y": log_y, + "rows": rows, + } + if json_filename is None: + json_filename = f"{_sanitize_filename(filename)}.json" + with open(json_filename, "w", encoding="utf-8") as handle: + json.dump(payload, handle, indent=2, sort_keys=True) + _plot_rows(rows, title, filename, x_label, log_x=log_x, log_y=log_y) + + + +if __name__ == "__main__": + plot_from_json = os.getenv("PLOT_FROM_JSON") + if plot_from_json: + if plot_from_json != "1": + json_paths = [p.strip() for p in plot_from_json.split(",") if p.strip()] + else: + json_paths = [ + f"{_sanitize_filename('time taken for various kappa values (10000 samples)')}.json", + f"{_sanitize_filename('time taken for various kappa values log scale (10000 samples)')}.json", + f"{_sanitize_filename('time taken for various sample counts log scale (kappa=10)')}.json", + f"{_sanitize_filename('time taken for various sample counts log scale (kappa=-10)')}.json", + ] + for json_path in json_paths: + plot_benches(json_path) + raise SystemExit(0) + + runner = pyperf.Runner() + + + mult_kappa = bench_multiple_kappa() + #mult_samples_neg_10 = bench_multiple_sample_counts(-10) + #mult_samples_10 = bench_multiple_sample_counts(10) + log_mult_samples_10 = bench_multiple_sample_counts_log(10) + log_mult_samples_neg_10 = bench_multiple_sample_counts_log(-10) + + + if not runner.args.worker: + plot_benches(mult_kappa, "time taken for various kappa values (10000 samples)", "time taken for various kappa values (10000 samples)", "kappa") + plot_benches(mult_kappa, "time taken for various kappa values log scale (10000 samples)", "time taken for various kappa values log scale (10000 samples)", "kappa", log_y=True) + + #plot_benches(mult_samples_10, "time taken for various sample counts (kappa=10)", "time taken for various sample counts (kappa=10)", "sample_count") + plot_benches(log_mult_samples_10, "time taken for various sample counts (kappa=10)", "time taken for various sample counts log scale (kappa=10)", "sample_count", log_x=True, log_y=True) + + #plot_benches(mult_samples_neg_10, "time taken for various sample counts (kappa=-10)", "time taken for various sample counts (kappa=-10)", "sample_count") + plot_benches(log_mult_samples_neg_10, "time taken for various sample counts (kappa=-10)", "time taken for various sample counts log scale (kappa=-10)", "sample_count", log_x=True, log_y=True) diff --git a/model/distributions/sphere/watson/cartesian.py b/model/distributions/sphere/watson/cartesian.py new file mode 100644 index 0000000..49f7e21 --- /dev/null +++ b/model/distributions/sphere/watson/cartesian.py @@ -0,0 +1,59 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy +from scipy.special import erf, erfi, erfinv + + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from model.distributions.sphere.watson.fibonachi import WatsonFibonachiSampling as wf +from util.selectors.slider_square import SliderSquare +from util.cartesian_util import CartesianUtil as cu +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class WatsonCartesianSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 64, 100, 4)) + ] + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + # map cartesian grid to watson, using inverse cdf closed form, see fibonachi.py for reference + + # for kappa = 0, w=cos(theta) is uniform in [−1,1] and phi is uniform in [0,2pi] + # this is contrary to uniform_samping where theta is uniform in [0, pi] and phi in [0, 2pi] + + + sample_count = sample_options[0].state + n = int(np.sqrt(sample_count)) + k = distribution_options[0].state # kappa + + grid = cu.generate_cartesian_grid(n, (1, 1)) + x, y = grid[:,0], grid[:,1] + x = 2*x -1 # map x from [0,1] to [-1, 1] + phi = 2 * np.pi * y # azimuthal angle, [0, 2pi] uniform + + if k > 0: + w = 1 / (np.sqrt(k)) * wf.erfi_inv( x * erfi(np.sqrt(k)) ) + elif k < 0: + la = -k + w = 1 / (np.sqrt(la)) * erfinv( x * erf(np.sqrt(la)) ) + elif k == 0: + w = x + + + w = np.clip(w, -1.0, 1.0) # clamp to avoid sqrt warnings due to numerical issues + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( phi) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( phi) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + return x_i_f + + diff --git a/model/distributions/sphere/watson/fibonachi.py b/model/distributions/sphere/watson/fibonachi.py new file mode 100644 index 0000000..30895bd --- /dev/null +++ b/model/distributions/sphere/watson/fibonachi.py @@ -0,0 +1,281 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy +import scipy.integrate +import scipy.interpolate +import sphstat +from pyrecest.backend import array +from pyrecest.distributions import WatsonDistribution as WatsonDistributionPyrecest +from scipy.special import erf, erfi, erfinv + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from model.sphere.sphere import Sphere +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + + +class WatsonFibonachiSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)), + ] + + def get_name(self): + return "Fibonacci-Kronecker Lattice" + + + def sample(self, sample_options, distribution_options): + return self.sample_closed(sample_options, distribution_options) + + def sample_inverse_interpolation(self, sample_options, distribution_options): + kappa = distribution_options[0].state + sample_count = sample_options[0].state + + mu = array([0.0, 0.0, 1.0]) + watson_dist = WatsonDistributionPyrecest(mu=mu, kappa=kappa) + + + ''' + Note: the watson pdf is symetrical + This means that it will always produce the same value for different + azimuth angles (phi) at a given polar angle (theta). + ''' + def pdf(phi, theta): + # polar angle: 0 ≤ θ ≤ π (theta) + # azimuth: 0 ≤ φ < 2π (phi) + x, y, z = Sphere.spherical_to_cartesian(theta=theta, phi=phi) + x = np.column_stack((x, y, z)) + wts = watson_dist.pdf(array(x)) + return wts + + def f(t,y): + # ring at lattitude theta has radius 2pi * sin(theta) + # 2pi comes from integrating over phi from 0 to 2pi + # sin(theta): d S^2 = sin(theta) d theta d phi + return 2* np.pi * pdf(0, t) * np.sin(t) # choose phi = 0 because of symmetry + + t_span = (0, np.pi) # theta from 0 to pi + y0 = 0 # the value of the integrated pdf at 0 is 0 + + sol = scipy.integrate.solve_ivp(f, t_span, [y0], rtol=1e-9, atol=1e-12) + + x = sol.t + y = sol.y[0] + + + # due to numerical issues, for large kappa and samplecount, y can be slightly non monotonic + # monotonicity is needed for interpolation, so maximum.accumulate then bump by eps + y = np.maximum.accumulate(y) + y /= y[-1] # normalize to [0,1] + eps = 1e-14 + y += eps * np.arange(len(y)) + + # now interpolate, but we swamp x and y so whe get the inverse function + # this works because the function is monotonic + # use PCHIP interpolation + q = scipy.interpolate.PchipInterpolator(x=y, y=x) + + + i = np.linspace(0, 1, sample_count, endpoint=False) + 0.5/sample_count # avoid poles by using centered kronecker lattice variant + theta_i = q(i) + w = np.cos(theta_i) + + indices = np.arange(0, sample_count) + gold_seq = (1+5**0.5)/2 # golden ratio + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( (2 * np.pi * indices) / gold_seq) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( (2 * np.pi * indices) / gold_seq) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + return x_i_f + + # same as sample, but using inverse ODE to solve, simmilar to https://isas.iar.kit.edu/pdf/FUSION25_Frisch.pdf Sec. V.D + # doesn't work for really big kappa due to numerical issues + def sample_inverse_ode(self, sample_options, distribution_options): + kappa = distribution_options[0].state + sample_count = sample_options[0].state + + mu = array([0.0, 0.0, 1.0]) + watson_dist = WatsonDistributionPyrecest(mu=mu, kappa=kappa) + + + ''' + Note: the watson pdf is symetrical + This means that it will always produce the same value for different + azimuth angles (phi) at a given polar angle (theta). + ''' + def pdf(phi, theta): + # polar angle: 0 ≤ θ ≤ π (theta) + # azimuth: 0 ≤ φ < 2π (phi) + x, y, z = Sphere.spherical_to_cartesian(theta=theta, phi=phi) + x = np.column_stack((x, y, z)) + wts = watson_dist.pdf(array(x)) + return wts + + # compute this is the inverse of the f from the above sample() method + def f(p,w): + # w = cos(theta), because sin(theta) is 0 for 0 and pi => divide by 0 + w = np.clip(w, -1, 1) + theta = np.arccos(w) + return 1/ ( (2*np.pi) * pdf(0, theta)) # choose phi = 0 because of symmetry + + + i = np.linspace(0, 1, sample_count) + + p_span = (0,1) + y0 = -1 + + + # now compute the ode + sol = scipy.integrate.solve_ivp(f, p_span, [y0], t_eval=i, dense_output=True, method="Radau") + + i = np.linspace(0, 1, sample_count, endpoint=False) + 0.5/sample_count + w = sol.sol(i) # shape (1,n) + w = np.squeeze(w) + + indices = np.arange(0, sample_count) + gold_seq = (1+5**0.5)/2 # golden ratio + + w = np.clip(w, -1, 1) # needed due to floating point impresision + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( (2 * np.pi * indices) / gold_seq) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( (2 * np.pi * indices) / gold_seq) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + return x_i_f + + @staticmethod + def erfi_inv(y, iters=8, thresh=1.5): + y = np.asarray(y, dtype=float) + sgn = np.sign(y) + z = np.abs(y) + + x = np.zeros_like(z) + mask = z > 0 + if not np.any(mask): + return x # all zeros + + z_nz = z[mask] + x0 = np.empty_like(z_nz) + + # Small region: Taylor + small = z_nz <= thresh + large = ~small + + x0[small] = z_nz[small] * np.sqrt(np.pi) / 2.0 + + # Large region: leading asymptotic, ignoring 1/x + if np.any(large): + t = np.log(z_nz[large] * np.sqrt(np.pi)) + t = np.maximum(t, 0.0) + x0[large] = np.sqrt(t) + + # Newton + x_new = x0 + for _ in range(iters): + fx = erfi(x_new) - z_nz + dfx = 2.0 / np.sqrt(np.pi) * np.exp(x_new**2) + x_new = x_new - fx / dfx + + x[mask] = x_new + return sgn * x + + + def sample_closed(self, sample_options, distribution_options): + + sample_count = sample_options[0].state + k = distribution_options[0].state # kappa + + gold_seq = (1+5**0.5)/2 # golden ratio + + indices = np.arange(0, sample_count) + + + if k > 0: + w = 1 / (np.sqrt(k)) * self.erfi_inv( ((1-2*indices + sample_count)/ sample_count) * erfi(np.sqrt(k)) ) + elif k < 0: + la = -k + w = 1 / (np.sqrt(la)) * erfinv( ((2*indices +1 - sample_count)/ sample_count) * erf(np.sqrt(la)) ) + elif k == 0: + w = ((2*indices +1 - sample_count)/ sample_count) + + + w = np.clip(w, -1.0, 1.0) # clamp to avoid sqrt warnings due to numerical issues + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( (2 * np.pi * indices) / gold_seq) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( (2 * np.pi * indices) / gold_seq) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) + + return x_i_f + + + def sample_events(self, sample_options, distribution_options): + kappa = distribution_options[0].state + sample_count = sample_options[0].state + + mu = array([0.0, 0.0, 1.0]) + watson_dist = WatsonDistributionPyrecest(mu=mu, kappa=kappa) + + + ''' + Note: the watson pdf is symetrical + This means that it will always produce the same value for different + azimuth angles (phi) at a given polar angle (theta). + ''' + def pdf(phi, theta): + # polar angle: 0 ≤ θ ≤ π (theta) + # azimuth: 0 ≤ φ < 2π (phi) + x, y, z = Sphere.spherical_to_cartesian(theta=theta, phi=phi) + x = np.column_stack((x, y, z)) + wts = watson_dist.pdf(array(x)) + return wts + + def f(t,y): + # ring at lattitude theta has radius 2pi * sin(theta) + # 2pi comes from integrating over phi from 0 to 2pi + # sin(theta): d S^2 = sin(theta) d theta d phi + return 2* np.pi * pdf(0, t) * np.sin(t) # choose phi = 0 because of symmetry + + t_span = (0, np.pi) # theta from 0 to pi + y0 = 0 # the value of the integrated pdf at 0 is 0 + + + # targets for events (centered variant, see above coment) + i = np.linspace(0, 1, sample_count, endpoint=False) + 0.5/sample_count + events = [] + for target in i: + def make_event(target): + def event(t, y): + return y[0] - target + return event + event = make_event(target) + event.terminal = False + event.direction = 1 # increasing + events.append(event) + + + sol = scipy.integrate.solve_ivp(f, t_span, [y0], events=events) + try: + event_thetas_i = np.array(sol.t_events).squeeze() + except ValueError: + # this sometimes happens for large samples counts, like kappa=-30 with 10k samples misses 2points + # its probably fine to continue + print("Warning: some points might have been missed") + event_thetas_i = np.array([te[0] if te.size else np.nan for te in sol.t_events], float) + event_thetas_i = event_thetas_i[~np.isnan(event_thetas_i)] + + sample_count = event_thetas_i.shape[0] + + w = np.cos(event_thetas_i) + + indices = np.arange(0, sample_count) + gold_seq = (1+5**0.5)/2 # golden ratio + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( (2 * np.pi * indices) / gold_seq) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( (2 * np.pi * indices) / gold_seq) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + return x_i_f + diff --git a/model/distributions/sphere/watson/fibonachi_rank1.py b/model/distributions/sphere/watson/fibonachi_rank1.py new file mode 100644 index 0000000..0018bf7 --- /dev/null +++ b/model/distributions/sphere/watson/fibonachi_rank1.py @@ -0,0 +1,58 @@ +from abc import ABC, abstractmethod +from matplotlib.pyplot import grid +import numpy as np +import scipy +import scipy.integrate +import scipy.interpolate +import sphstat +from pyrecest.backend import array +from pyrecest.distributions import WatsonDistribution as WatsonDistributionPyrecest +from scipy.special import erf, erfi, erfinv + +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.slider_fib import SliderFib +from model.sphere.sphere import Sphere +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from model.distributions.sphere.watson.fibonachi import WatsonFibonachiSampling as wf + + + +class WatsonFibonachiRank1Sampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderFib("Number of Samples", 3, 33, 21, 9, minus_1=True)), + ] + self.sampler = CylinderFibRank1UniformSampling() + + + def get_name(self): + return "Fibonacci-Rank-1 Lattice" + + + def sample(self, sample_options, distribution_options): + + sample_count = sample_options[0].state + 1 # because minus_1 is true slider displays fib(n)-1 + k = distribution_options[0].state # kappa + + x, y = self.sampler.get_rank_1(sample_count, sample_options[0].idx, without_first_point=True) + + x = 2*x -1 # map x from [0,1] to [-1, 1] + phi = 2 * np.pi * y # azimuthal angle, [0, 2pi] uniform + + if k > 0: + w = 1 / (np.sqrt(k)) * wf.erfi_inv( x * erfi(np.sqrt(k)) ) + elif k < 0: + la = -k + w = 1 / (np.sqrt(la)) * erfinv( x * erf(np.sqrt(la)) ) + elif k == 0: + w = x + + + w = np.clip(w, -1.0, 1.0) # clamp to avoid sqrt warnings due to numerical issues + + x_i_f_0 = w + x_i_f_1 = np.sqrt(1-w**2) * np.cos( phi) + x_i_f_2 = np.sqrt(1-w**2) * np.sin( phi) + x_i_f = np.column_stack((x_i_f_1, x_i_f_2, x_i_f_0)) # order so that mu=[0, 0, 1] + return x_i_f \ No newline at end of file diff --git a/model/distributions/sphere/watson/random_sampling.py b/model/distributions/sphere/watson/random_sampling.py new file mode 100644 index 0000000..3155e86 --- /dev/null +++ b/model/distributions/sphere/watson/random_sampling.py @@ -0,0 +1,50 @@ +from abc import ABC, abstractmethod +import numpy as np +import scipy +import sphstat + +from model.distributions.sphere.sphere_sampling_schema import SphereSamplingSchema +from util.selectors.silder_log import LogSlider +from model.sphere.sphere import Sphere +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + + +class WatsonRandomSampling(SphereSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)), + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + kappa = distribution_options[0].state + + theta = 0 # can be hardcoded because the user can just turn the sphere + phi = 0 + + numsamp = sample_options[0].state + if kappa == 0: # become uniform distribution + samples = np.random.normal(size=(numsamp, 3)) + samples /= np.linalg.norm(samples, axis=1)[:, np.newaxis] + return samples + + lamb, mu, nu = Sphere.spherical_to_cartesian(theta, phi) + + samples = sphstat.distributions.watson(numsamp, lamb, mu, nu, kappa)["points"] + if numsamp > 1: + m = len(samples) + n = m // 2 + assert numsamp == n, "Unexpected number of samples generated" + rng = np.random.default_rng(None) + pts = np.vstack(samples) # shape (2numsamp,3) + A = pts[:n] + B = pts[n:] + choose_B = rng.random(n) < 0.5 + out = np.where(choose_B[:, None], B, A) + return out + else: + samples_array = np.vstack(samples) + return samples_array \ No newline at end of file diff --git a/model/distributions/sphere/watson/watson.py b/model/distributions/sphere/watson/watson.py new file mode 100644 index 0000000..a525c71 --- /dev/null +++ b/model/distributions/sphere/watson/watson.py @@ -0,0 +1,45 @@ +from abc import ABC, abstractmethod +from model.distributions.sphere.sphere_distribution import SphereDistribution +from util.selectors.slider_float import FloatSlider +from util.selectors.slider import Slider +import numpy as np +import pyrecest._backend +from pyrecest.backend import array +from pyrecest.distributions import WatsonDistribution as WatsonDistributionPyrecest + +from model.distributions.sphere.watson.random_sampling import WatsonRandomSampling +from model.distributions.sphere.watson.fibonachi import WatsonFibonachiSampling +from model.distributions.sphere.watson.cartesian import WatsonCartesianSampling +from model.distributions.sphere.watson.fibonachi_rank1 import WatsonFibonachiRank1Sampling + + +class WatsonDistribution(SphereDistribution): + def __init__(self): + self.distribution_options = [ + Slider("κ (kappa)", -50, 10.0, 50.0), + ] + + self.sampling_methods = [ + WatsonRandomSampling(), + WatsonFibonachiSampling(), + WatsonCartesianSampling(), + WatsonFibonachiRank1Sampling(), + ] + + def get_name(self): + return "Watson" + + def get_pdf(self, distribution_options): + alpha = 0.5 # scale + kappa = distribution_options[0].state + mu = array([0.0, 0.0, 1.0]) + + watson_dist = WatsonDistributionPyrecest(mu=mu, kappa=kappa) + + def pdf(x): + wts = watson_dist.pdf(array(x)) + max = np.max(wts) + norm = wts / max + norm = norm * alpha + return norm + return pdf \ No newline at end of file diff --git a/model/distributions/torus/__init__.py b/model/distributions/torus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/torus/torus_distribution.py b/model/distributions/torus/torus_distribution.py new file mode 100644 index 0000000..9acba3e --- /dev/null +++ b/model/distributions/torus/torus_distribution.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod +from functools import cached_property +from model.distribution import Distribution + +class TorusDistribution(Distribution): + def __init__(self): + self.distribution_options = [] + # a list of objects that implement the torus_sampling_schema interface + # that can be used with this distribution + self.sampling_methods = [] + + + # Returns the name of the distribution + @abstractmethod + def get_name(self): + pass + + @cached_property + def sampling_method_dict(self): + return {m.get_name(): m for m in self.sampling_methods} + + # returns a functions that takes a (N, 3) shape nparray and returns the pdf value at the points + @abstractmethod + def get_pdf(self, distribution_options): + pass \ No newline at end of file diff --git a/model/distributions/torus/torus_sampling_schema.py b/model/distributions/torus/torus_sampling_schema.py new file mode 100644 index 0000000..ccebfef --- /dev/null +++ b/model/distributions/torus/torus_sampling_schema.py @@ -0,0 +1,17 @@ +from abc import ABC, abstractmethod +from model.sampling_schema import SamplingSchema + +class TorusSamplingSchema(SamplingSchema): + def __init__(self): + self.sample_options = [] + + # Returns the name of the sampling method + @abstractmethod + def get_name(self): + pass + + # returns samples as a numpy array of shape (n, 2) + # where (2) are the parameters (t, p) on the torus + @abstractmethod + def sample(self, sample_options, distribution_options): + pass \ No newline at end of file diff --git a/model/distributions/torus/uniform/__init__.py b/model/distributions/torus/uniform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/torus/uniform/cartesian.py b/model/distributions/torus/uniform/cartesian.py new file mode 100644 index 0000000..16f6e4f --- /dev/null +++ b/model/distributions/torus/uniform/cartesian.py @@ -0,0 +1,25 @@ +import numpy as np + +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.slider_square import SliderSquare +from util.cartesian_util import CartesianUtil as cu +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class TorusCartesianUniformSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 64, 100, 4)) + ] + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + n = int(np.sqrt(sample_count)) + return cu.generate_cartesian_grid(n, (2 * np.pi, 2 * np.pi)) \ No newline at end of file diff --git a/model/distributions/torus/uniform/fibonacci_kronecker.py b/model/distributions/torus/uniform/fibonacci_kronecker.py new file mode 100644 index 0000000..e2edd17 --- /dev/null +++ b/model/distributions/torus/uniform/fibonacci_kronecker.py @@ -0,0 +1,32 @@ +from abc import ABC, abstractmethod +import numpy as np + +from util.selectors.silder_log import LogSlider +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class TorusKroneckerUniformSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + self.info_md = """ + > Warning: Mapping the Kronecker lattice to the torus is not recommended in practice, as it is only periodic on one axis. + It is included for demonstration purposes only.""" + + def get_name(self): + return "Fibonacci-Kronecker Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + indices = np.arange(0, sample_count) + gol = (1+5**0.5)/2 + + # centered rank-1 lattice generator + equidistant_generator = (2 * indices + 1) / (2 * sample_count) + + z = equidistant_generator + p = (indices / gol) % 1 + + return np.column_stack((p * 2 * np.pi, z * 2 * np.pi)) \ No newline at end of file diff --git a/model/distributions/torus/uniform/fibonacci_rank_1.py b/model/distributions/torus/uniform/fibonacci_rank_1.py new file mode 100644 index 0000000..e555d3d --- /dev/null +++ b/model/distributions/torus/uniform/fibonacci_rank_1.py @@ -0,0 +1,26 @@ +from abc import ABC, abstractmethod +import numpy as np + +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_fib import SliderFib +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class TorusFibRank1UniformSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderFib("Number of Samples", 2, 34, 21, 9)) + ] + self.sampler = CylinderFibRank1UniformSampling() + + def get_name(self): + return "Fibonacci-Rank-1 Lattice" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + t, p = self.sampler.get_rank_1(sample_count, sample_options[0].idx) + + samples = np.column_stack((t * (2* np.pi), p * (2* np.pi))) + + return samples \ No newline at end of file diff --git a/model/distributions/torus/uniform/random.py b/model/distributions/torus/uniform/random.py new file mode 100644 index 0000000..015eddc --- /dev/null +++ b/model/distributions/torus/uniform/random.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_log import LogSlider +import numpy as np +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + + +class TorusRandomUniformSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + t = np.random.uniform(0, 2 * np.pi, sample_count) + p = np.random.uniform(0, 2 * np.pi, sample_count) + + samples = np.column_stack((t, p)) + + return samples \ No newline at end of file diff --git a/model/distributions/torus/uniform/uniform.py b/model/distributions/torus/uniform/uniform.py new file mode 100644 index 0000000..3db3829 --- /dev/null +++ b/model/distributions/torus/uniform/uniform.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +from util.selectors.slider import Slider +import numpy as np + +from model.distributions.torus.torus_distribution import TorusDistribution +from model.distributions.torus.uniform.random import TorusRandomUniformSampling +from model.distributions.torus.uniform.fibonacci_rank_1 import TorusFibRank1UniformSampling +from model.distributions.torus.uniform.fibonacci_kronecker import TorusKroneckerUniformSampling +from model.distributions.torus.uniform.cartesian import TorusCartesianUniformSampling +class UniformTorusDistribution(TorusDistribution): + def __init__(self): + self.distribution_options = [] + self.sampling_methods = [ + TorusRandomUniformSampling(), + TorusFibRank1UniformSampling(), + TorusKroneckerUniformSampling(), + TorusCartesianUniformSampling(), + ] + + + def get_name(self): + return "Uniform" + + def get_pdf(self, distribution_options): + scaling_factor = 5 # pdf plotted not to scale + def pdf(x): + N = np.shape(x)[0] + return (1 / (4 * np.pi * np.pi)) * np.ones(N) * scaling_factor + return pdf \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/__init__.py b/model/distributions/torus/wrapped_normal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distributions/torus/wrapped_normal/cartesian.py b/model/distributions/torus/wrapped_normal/cartesian.py new file mode 100644 index 0000000..d91de08 --- /dev/null +++ b/model/distributions/torus/wrapped_normal/cartesian.py @@ -0,0 +1,49 @@ +from abc import ABC, abstractmethod +import numpy as np +from scipy.stats import norm + +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_square import SliderSquare +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu +from util.cartesian_util import CartesianUtil as cu + +class TorusFibCartWNSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderSquare("Number of Samples", 4, 16, 100, 4)) + ] + self.sampler = CylinderFibRank1UniformSampling() + self.info_md = """ + > Warning: Using the Cartesian Grid is not recomended in practise, as it yields bad results. + > It is included for demonstration purposes only.""" + + def get_name(self): + return "Cartesian Grid" + + def sample(self, sample_options, distribution_options): + # see https://isas.iar.kit.edu/pdf/Fusion21_Frisch.pdf + sample_count = sample_options[0].state + + n = int(np.sqrt(sample_count)) + + grid = cu.generate_cartesian_grid(n, (1.0, 1.0)) + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.transform_grid_gaussian(grid, (mean_x, mean_y), Cov) + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + gaus_grid[:,1] = gaus_grid[:,1] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/fibonacci.py b/model/distributions/torus/wrapped_normal/fibonacci.py new file mode 100644 index 0000000..368a126 --- /dev/null +++ b/model/distributions/torus/wrapped_normal/fibonacci.py @@ -0,0 +1,48 @@ +from abc import ABC, abstractmethod +import numpy as np +from scipy.stats import norm + +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_fib import SliderFib +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + +class TorusFibRank1WNSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(SliderFib("Number of Samples", 3, 33, 21, 9, minus_1=True)) + ] + self.sampler = CylinderFibRank1UniformSampling() + + def get_name(self): + return "Fibonacci-Rank-1 Lattice" + + def sample(self, sample_options, distribution_options): + # see https://isas.iar.kit.edu/pdf/Fusion21_Frisch.pdf + sample_count = sample_options[0].state + 1 # because minus_1 is true slider displays fib(n)-1 + + t, p = self.sampler.get_rank_1(sample_count, sample_options[0].idx, without_first_point=True) + + fib_grid = np.column_stack((t , p)) + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.transform_grid_gaussian(fib_grid, (mean_x, mean_y), Cov) + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + gaus_grid[:,1] = gaus_grid[:,1] % (2 * np.pi) + return gaus_grid + + + \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/fibonacci_frolov.py b/model/distributions/torus/wrapped_normal/fibonacci_frolov.py new file mode 100644 index 0000000..78112d6 --- /dev/null +++ b/model/distributions/torus/wrapped_normal/fibonacci_frolov.py @@ -0,0 +1,43 @@ +import numpy as np +from deterministic_gaussian_sampling_fibonacci import sample_gaussian_fibonacci + +from util.selectors.silder_log import LogSlider +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class FFrolovWNSampling(TorusSamplingSchema): + def __init__(self): + def _check_input(val): + return val >= 1 and val <= 100003 and isinstance(val, int) + + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Fibonacci Frolov" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + mu = np.array([mean_x, mean_y]) + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.sample_frolov_gaussian(mu, Cov, sample_count, "Fibonacci") + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + gaus_grid[:,1] = gaus_grid[:,1] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/fibonacci_kronecker.py b/model/distributions/torus/wrapped_normal/fibonacci_kronecker.py new file mode 100644 index 0000000..28bdc16 --- /dev/null +++ b/model/distributions/torus/wrapped_normal/fibonacci_kronecker.py @@ -0,0 +1,52 @@ +import numpy as np + +from util.selectors.silder_log import LogSlider +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class TorusFibKroneckerWNSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Fibonacci-Kronecker Lattice" + + def sample(self, sample_options, distribution_options): + # see https://isas.iar.kit.edu/pdf/Fusion21_Frisch.pdf + sample_count = sample_options[0].state + + indices = np.arange(0, sample_count) + indices_p1 = np.arange(0, sample_count + 1) + gol = (1+5**0.5)/2 + + # centered rank-1 lattice generator + equidistant_generator = (2 * indices + 1) / (2 * sample_count) + + t = equidistant_generator + p = (indices_p1 / gol) % 1 + p = p[1:] + + + fib_grid = np.column_stack((t , p)) + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.transform_grid_gaussian(fib_grid, (mean_x, mean_y), Cov) + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + gaus_grid[:,1] = gaus_grid[:,1] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/frolov.py b/model/distributions/torus/wrapped_normal/frolov.py new file mode 100644 index 0000000..524309f --- /dev/null +++ b/model/distributions/torus/wrapped_normal/frolov.py @@ -0,0 +1,43 @@ +import numpy as np +from deterministic_gaussian_sampling_fibonacci import sample_gaussian_fibonacci + +from util.selectors.silder_log import LogSlider +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class CFrolovWNSampling(TorusSamplingSchema): + def __init__(self): + def _check_input(val): + return val >= 1 and val <= 100003 and isinstance(val, int) + + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Classical Frolov" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + mu = np.array([mean_x, mean_y]) + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.sample_frolov_gaussian(mu, Cov, sample_count, "ClassicalFrolov") + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + gaus_grid[:,1] = gaus_grid[:,1] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/improved_frolov.py b/model/distributions/torus/wrapped_normal/improved_frolov.py new file mode 100644 index 0000000..a9cf852 --- /dev/null +++ b/model/distributions/torus/wrapped_normal/improved_frolov.py @@ -0,0 +1,43 @@ +import numpy as np +from deterministic_gaussian_sampling_fibonacci import sample_gaussian_fibonacci + +from util.selectors.silder_log import LogSlider +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI +from util.gaus_util import GausUtil as gu + + +class IFrolovWNSampling(TorusSamplingSchema): + def __init__(self): + def _check_input(val): + return val >= 1 and val <= 100003 and isinstance(val, int) + + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Improved Frolov" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + mu = np.array([mean_x, mean_y]) + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + gaus_grid = gu.sample_frolov_gaussian(mu, Cov, sample_count, "ImprovedFrolov") + + # wrapp + gaus_grid[:,0] = gaus_grid[:,0] % (2 * np.pi) + gaus_grid[:,1] = gaus_grid[:,1] % (2 * np.pi) + return gaus_grid \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/random.py b/model/distributions/torus/wrapped_normal/random.py new file mode 100644 index 0000000..117ab5f --- /dev/null +++ b/model/distributions/torus/wrapped_normal/random.py @@ -0,0 +1,37 @@ +from abc import ABC, abstractmethod +from model.distributions.torus.torus_sampling_schema import TorusSamplingSchema +from util.selectors.silder_log import LogSlider +import numpy as np +from util.selectors.silder_manual_input_wrapper import SliderManualInputWrapper as MI + +class TorusRandomWrappedSampling(TorusSamplingSchema): + def __init__(self): + self.sample_options = [ + MI(LogSlider("Number of Samples", 10, 100, 10000)) + ] + + def get_name(self): + return "Random" + + def sample(self, sample_options, distribution_options): + sample_count = sample_options[0].state + + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + mean = np.array([mean_x, mean_y]) + + samples = np.random.multivariate_normal(mean, Cov, sample_count) + samples[:,0] = samples[:,0] % (2 * np.pi) + samples[:,1] = samples[:,1] % (2 * np.pi) + + + return samples \ No newline at end of file diff --git a/model/distributions/torus/wrapped_normal/wrapped_normal.py b/model/distributions/torus/wrapped_normal/wrapped_normal.py new file mode 100644 index 0000000..2b03bfe --- /dev/null +++ b/model/distributions/torus/wrapped_normal/wrapped_normal.py @@ -0,0 +1,76 @@ +import numpy as np +from scipy.stats import multivariate_normal + +from util.selectors.slider_float import FloatSlider +from util.selectors.slider_pi import PiSlider +from model.distributions.torus.torus_distribution import TorusDistribution +from model.distributions.torus.wrapped_normal.random import TorusRandomWrappedSampling +from model.distributions.torus.wrapped_normal.fibonacci import TorusFibRank1WNSampling +from model.distributions.torus.wrapped_normal.fibonacci_kronecker import TorusFibKroneckerWNSampling +from model.distributions.torus.wrapped_normal.cartesian import TorusFibCartWNSampling +from model.distributions.torus.wrapped_normal.frolov import CFrolovWNSampling +from model.distributions.torus.wrapped_normal.improved_frolov import IFrolovWNSampling +from model.distributions.torus.wrapped_normal.fibonacci_frolov import FFrolovWNSampling +from model.torus.torus import Torus +class WrappedNormalTorusDistribution(TorusDistribution): + def __init__(self): + self.distribution_options = [ + PiSlider("Mean p (μₚ)", 0, 1, 2), + PiSlider("Mean t (μₜ)", 0, 1, 2), + FloatSlider("Sigma p (σₚ)", 0, 0.5, 5.0), + FloatSlider("Sigma t (σₜ)", 0, 0.5, 5.0), + FloatSlider("Correlation (ρ)", -1, 0.1, 1), + ] + self.sampling_methods = [ + TorusRandomWrappedSampling(), + TorusFibRank1WNSampling(), + TorusFibKroneckerWNSampling(), + TorusFibCartWNSampling(), + CFrolovWNSampling(), + IFrolovWNSampling(), + FFrolovWNSampling(), + ] + + + def get_name(self): + return "Wrapped Normal" + + def get_pdf(self, distribution_options): + mean_x = distribution_options[0].state + mean_y = distribution_options[1].state + sigma_t = distribution_options[2].state + sigma_p = distribution_options[3].state + correlation = distribution_options[4].state + + Cov = np.array([ + [sigma_t**2, correlation * sigma_t * sigma_p], + [correlation * sigma_t * sigma_p, sigma_p**2] + ]) + + mean = np.array([mean_x, mean_y]) + + dist = multivariate_normal(mean=mean, cov=Cov, allow_singular=True) + + def pdf(x): + alpha = 0.7 # scale + + t, p = Torus.xyz_to_t_p(x[:,0], x[:,1], x[:,2]) + t_p = np.column_stack((t,p)) + + # wrap until 3 * sigma_x + k_t = int(np.ceil(3 * sigma_t / (2 * np.pi))) + k_p = int(np.ceil(3 * sigma_p / (2 * np.pi))) + + total_pdf = np.zeros(t_p.shape[0]) + for i in range(-k_t, k_t+1): + for j in range(-k_p, k_p+1): + shifted_samples = np.column_stack((t_p[:,0] + i * 2 * np.pi, t_p[:,1] + j * 2 * np.pi)) + total_pdf += dist.pdf(shifted_samples) + + max = np.max(total_pdf) + + norm = total_pdf / max + norm = norm * alpha + return norm + + return pdf \ No newline at end of file diff --git a/model/manifold.py b/model/manifold.py new file mode 100644 index 0000000..01849bf --- /dev/null +++ b/model/manifold.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod + +class Manifold(ABC): + # optional initial settings for 3d camera + @property + def camera_settings_3d(self): + return getattr(self, "_camera_settings_3d", None) + + @camera_settings_3d.setter + def camera_settings_3d(self, value): + self._camera_settings_3d = value + + # optional, setting for if the manifold supports 2d plotting + @property + def plot_settings_2d(self): + return getattr(self, "_plot_settings_2d", None) + + @plot_settings_2d.setter + def plot_settings_2d(self, value): + self._plot_settings_2d = value + + # generates renderable xyz grid + @abstractmethod + def generate_xyz(self, *args, **kwargs): + pass + + # returns samples based on selected distribution and sample options + # samples are returned as a tuple (saples_xyz, samples_2d | None) + # also converts the sample output type to xyz coordinates + @abstractmethod + def update_sample(self, selected_distribution, sample_options): + pass + + def generate_mesh(self, pdf, *args, **kwargs): + pass + + # uses the objects pararametrization to convert 2d coords to 3d coords and apply pdf + # optional, only if the manifold supports 2d plotting + def pdf_2d(self, xy, pdf): + pass \ No newline at end of file diff --git a/model/sampling_schema.py b/model/sampling_schema.py new file mode 100644 index 0000000..7f42ccd --- /dev/null +++ b/model/sampling_schema.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod + +class SamplingSchema(ABC): + def __init__(self): + self.sample_options = [] + + # Returns the name of the sampling method + @abstractmethod + def get_name(self): + pass + + @abstractmethod + def sample(self, sample_options, distribution_options): + pass + + @property + def info_md(self): + return getattr(self, "_info_md", "") + + @info_md.setter + def info_md(self, value): + self._info_md = value \ No newline at end of file diff --git a/model/selfcontained_distribution.py b/model/selfcontained_distribution.py new file mode 100644 index 0000000..60a5dd2 --- /dev/null +++ b/model/selfcontained_distribution.py @@ -0,0 +1,12 @@ + +""" +This is a class of distributions that do not utelize the dynamic distribution loading system, +and provide all their callbacks and data internally. + +Meant to be used for simple distributions, where using the distribution loading system would be overkill. +To be used with the selfcontained_distribution_renderer. +""" +class SelfContainedDistribution: + def __init__(self): + self.settings_layout = [] # to be set by subclass + self.plot_layout = [] # to be set by subclass \ No newline at end of file diff --git a/model/sphere/__init__.py b/model/sphere/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/sphere/sphere.py b/model/sphere/sphere.py new file mode 100644 index 0000000..fdf1140 --- /dev/null +++ b/model/sphere/sphere.py @@ -0,0 +1,78 @@ +import numpy as np +import numpy as np +from scipy.spatial import ConvexHull +import plotly.figure_factory as ff + +from model.distributions.distribution_loader import DistributionLoader +from model.distributions.sphere.sphere_distribution import SphereDistribution +from model.manifold import Manifold +from model.distributions.sphere.uniform.fibonachi_lattice import SphereUniformFibSampling +from util.selectors.slider import Slider + +class Sphere(Manifold): + def __init__(self, resolution=200, radius=0.999): + self.xyz = self.generate_xyz(resolution, radius) + + self.distributions = DistributionLoader(SphereDistribution, "model.distributions.sphere").get_distributions() + + + self.mesh_xyz = self._init_mesh() + + + + def generate_xyz(self, resolution=50, radius=1): + phi = np.linspace(0, np.pi, resolution) + theta = np.linspace(0, 2 * np.pi, resolution) + phi, theta = np.meshgrid(phi, theta) + + x = radius * np.sin(phi) * np.cos(theta) + y = radius * np.sin(phi) * np.sin(theta) + z = radius * np.cos(phi) + + return x, y, z + + def update_sample(self, selected_distribution, selected_sampling_method, sample_options, distribution_options): + dist = self.distributions[selected_distribution] + sampling_method = dist.sampling_method_dict[selected_sampling_method] + return (sampling_method.sample(sample_options, distribution_options), None) + + + def generate_mesh(self, pdf, alpha=1): + # mesh_xyz has nans for line segments, mask before passing to pdf + mask = np.all(np.isfinite(self.mesh_xyz), axis=-1) + dens = pdf(self.mesh_xyz[mask]) + + xyz_extruded = np.full_like(self.mesh_xyz, np.nan, dtype=float) # full of nans + + xyz_extruded[mask] = self.mesh_xyz[mask] * (1 + alpha * dens[:, np.newaxis]) + return xyz_extruded[:,0], xyz_extruded[:,1], xyz_extruded[:,2] + + def _init_mesh(self, resolution=3000): + xyz = SphereUniformFibSampling.sample(None, [Slider("Number of Samples", 10, resolution, resolution)] , []) + x, y, z = xyz[:,0], xyz[:,1], xyz[:,2] + + hull = ConvexHull(xyz) + simplices = hull.simplices + + def cf(xi, yi, zi, zmin=np.min(z), zmax=np.max(z)): + if zi > zmax: + zi = np.nextafter(zmax, zmin) + return zi + + fig = ff.create_trisurf(x=x, y=y, z=z, + simplices=simplices, + show_colorbar=False, + color_func=cf + ) + + arr = np.column_stack((fig.data[1].x, fig.data[1].y, fig.data[1].z)) + return np.where(arr == None, np.nan, arr).astype(float) # line segments have None in them, put them to nan so we can do mult later + + @staticmethod + def spherical_to_cartesian(theta, phi, r=1): + x = r * np.sin(theta) * np.cos(phi) + y = r * np.sin(theta) * np.sin(phi) + z = r * np.cos(theta) + + return x, y, z + diff --git a/model/torus/__init__.py b/model/torus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/torus/torus.py b/model/torus/torus.py new file mode 100644 index 0000000..c4376b0 --- /dev/null +++ b/model/torus/torus.py @@ -0,0 +1,165 @@ +import numpy as np +import plotly.figure_factory as ff +from scipy.spatial import Delaunay + +from model.distributions.distribution_loader import DistributionLoader +from model.distributions.torus.torus_distribution import TorusDistribution +from model.manifold import Manifold +from renderer.plot_settings_2d import PlotSettings2D +from model.distributions.cylinder.uniform.fibonacci_rank_1 import CylinderFibRank1UniformSampling +from util.selectors.slider_fib import SliderFib + +class Torus(Manifold): + def __init__(self, resolution=100, r=1, R=3): + self.xyz = self.generate_xyz(resolution, r - 0.01, R - 0.01) # slightly smaller to avoid artifacts from mesh + + self.distributions = DistributionLoader(TorusDistribution, "model.distributions.torus").get_distributions() + + self.r = r + self.R = R + + self.mesh_xyz = self._init_mesh() + + axes_2d = ( + np.arange(0, 2.5 * np.pi, np.pi / 2), # 0, π/2, π, 3π/2, 2π + ["0", "π/2", "π", "3π/2", "2π"] + ) + self.plot_settings_2d = PlotSettings2D( + axes_2d_x=axes_2d, + axes_2d_y=axes_2d, + lock_aspect_ratio=True, + periodic_x=True, + periodic_y=True, + periodic_x_amount=2 * np.pi, + periodic_y_amount=2 * np.pi, + x_title="t", + y_title="p", + reverse_x_y_axis=True, # x is p, y is t + color_location=(0,0,2*np.pi, 2*np.pi), + ) + + def generate_xyz(self, resolution=50, r=1, R=3): + t = np.linspace(0, 2*np.pi, resolution) + p = np.linspace(0, 2*np.pi, resolution) + t,p = np.meshgrid(t,p) + + + return self.t_p_to_xyz(t, p, r, R) + + def pdf_2d(self, xy, pdf): + x, y, z = self.t_p_to_xyz(xy[:,0], xy[:,1], self.r, self.R) + return pdf(np.column_stack((x, y, z))) + + + def update_sample(self, selected_distribution, selected_sampling_method, sample_options, distribution_options): + dist = self.distributions[selected_distribution] + sampling_method = dist.sampling_method_dict[selected_sampling_method] + new_sample = sampling_method.sample(sample_options, distribution_options) + + if (new_sample is None) or new_sample.size == 0: + samples = np.empty((0, 3), dtype=float) + samples_2d = np.empty((0, 2), dtype=float) + return (samples, samples_2d) + + x, y, z = self.t_p_to_xyz(new_sample[:,0], new_sample[:,1], self.r, self.R) + + samples = np.column_stack((x, y, z)) + samples_2d = new_sample + return (samples, samples_2d) + + + @staticmethod + def t_p_to_xyz(t, p, r=1, R=3): + x = (R + r * np.cos(p)) * np.cos(t) + y = (R + r * np.cos(p)) * np.sin(t) + z = r * np.sin(p) + + return x, y, z + + @staticmethod + def xyz_to_t_p(x, y, z, r=1, R=3): + t = np.arctan2(y, x) + p = np.arctan2(z, np.sqrt(x**2 + y**2) - R) + return t, p + + + + def generate_mesh(self, pdf, alpha=1): + #return self.mesh_xyz[:,0], self.mesh_xyz[:,1], self.mesh_xyz[:,2] + + # mesh_xyz has nans for line segments, mask before passing to pdf + mask = np.all(np.isfinite(self.mesh_xyz), axis=-1) + dens = pdf(self.mesh_xyz[mask]) + + xyz_extruded = np.full_like(self.mesh_xyz, np.nan, dtype=float) # full of nans + tp = self.xyz_to_t_p(self.mesh_xyz[mask][:,0], self.mesh_xyz[mask][:,1], self.mesh_xyz[mask][:,2], self.r, self.R) + xyz_new = self.t_p_to_xyz(tp[0], tp[1], self.r * (1 + alpha * dens), self.R) + xyz_extruded[mask] = np.stack(xyz_new, axis=1) + return xyz_extruded[:,0], xyz_extruded[:,1], xyz_extruded[:,2] + + + def _init_mesh(self, resolution=(4181, 19)): + + #tp = CylinderFibRank1UniformSampling.sample(None, [SliderFib("Number of Samples", 10, resolution[0], resolution[0], resolution[1])] , []) + k = resolution[1] + samp_count = resolution[0] + t, p = CylinderFibRank1UniformSampling.get_rank_1(samp_count, k) + tp = np.column_stack((t * 2 * np.pi, p * 2 * np.pi)) + + x, y, z = self.t_p_to_xyz(tp[:,0], tp[:,1], self.r) + + + + def simplices_with_mask(_tp): + _simplices = Delaunay(_tp).simplices + + # only inner, avoid pi/4 border + ok_mask = (_tp[:, 0] > np.pi / 4) & (_tp[:, 0] < 7 * np.pi / 4) & (_tp[:, 1] > np.pi / 4) & (_tp[:, 1] < 7 * np.pi / 4) + ok_idx = np.where(ok_mask)[0] + + # throw away all triangles around the border + good_vertices = np.isin(_simplices, ok_idx) + good_triangles = good_vertices.any(axis=1) + _simplices = _simplices[good_triangles] + return _simplices + + + # shift p by pi/2 and cluster 4 quadrants with overlap + tp1 = np.copy(tp) + tp1[:,0] = (tp1[:,0]+(np.pi/2)) % (2 * np.pi) + tp1[:,1] = (tp1[:,1]+(np.pi/2)) % (2 * np.pi) + simplices_1 = simplices_with_mask(tp1) + + tp2 = np.copy(tp) + tp2[:,0] = (tp2[:,0]+(np.pi/2)) % (2 * np.pi) + tp2[:,1] = (tp2[:,1]-(np.pi/2)) % (2 * np.pi) + simplices_2 = simplices_with_mask(tp2) + + tp3 = np.copy(tp) + tp3[:,0] = (tp3[:,0]-(np.pi/2)) % (2 * np.pi) + tp3[:,1] = (tp3[:,1]-(np.pi/2)) % (2 * np.pi) + simplices_3 = simplices_with_mask(tp3) + + tp4 = np.copy(tp) + tp4[:,0] = (tp4[:,0]-(np.pi/2)) % (2 * np.pi) + tp4[:,1] = (tp4[:,1]+(np.pi/2)) % (2 * np.pi) + simplices_4 = simplices_with_mask(tp4) + + # merge all four quadrants + simplices_merged = np.vstack((simplices_1, simplices_2, simplices_3, simplices_4)) + simplices_merged = np.unique(simplices_merged, axis=0) + + def cf(xi, yi, zi, zmin=np.min(z), zmax=np.max(z)): + if zi > zmax: + zi = np.nextafter(zmax, zmin) + return zi + + fig = ff.create_trisurf(x=x, y=y, z=z, + simplices=simplices_merged, + show_colorbar=False, + color_func=cf + ) + + arr = np.column_stack((fig.data[1].x, fig.data[1].y, fig.data[1].z)) + return np.where(arr == None, np.nan, arr).astype(float) # line segments have None in them, put them to nan so we can do mult later + diff --git a/pages/conditional.py b/pages/conditional.py index 2477c1b..11d5a6a 100644 --- a/pages/conditional.py +++ b/pages/conditional.py @@ -1,179 +1,22 @@ -import plotly import dash -from dash import dcc, html, Input, Output, callback, Patch, callback_context -import plotly.graph_objects as go -import dash_bootstrap_components as dbc -from numpy import sqrt, linspace, pi, sign, exp, square, array, diff, matmul, zeros, meshgrid -from numpy.random import randint -from numpy.linalg import det, solve -dash.register_page(__name__) - -# Colors -col_marginal = plotly.colors.qualitative.Plotly[0] -col_conditional = plotly.colors.qualitative.Plotly[1] -col_slice = plotly.colors.qualitative.Plotly[4] -# axis limits -rangx = [-4, 4] -rangy = [-4, 4] -# slider range -smin = rangy[0] -smax = rangy[1] -# plot size relative to window size -relwidth = 95 -relheight = round((relwidth/diff(rangx)*diff(rangy))[0]) - -# Grid -xv = linspace(rangx[0], rangx[1], 100) -yv = linspace(rangy[0], rangy[1], 100) -xm, ym = meshgrid(xv, yv) - - -def gauss1(x, μ, C): - return 1/sqrt(2*pi*C) * exp(-1/2 * square((x-μ))/C) - - -def gauss2(x, y, μ, C): - d = array([x-μ[0], y-μ[1]]) - d = d.reshape(-1, 1) # to column vector - f = 1/sqrt(det(2*pi*C)) * exp(-1/2 * matmul(d.T, solve(C, d))) - return f[0][0] - - -# Initialize Plot -# https://plotly.com/python/3d-surface-plots/ -fig = go.Figure() -fig.add_trace(go.Surface(name='Joint f(x,y)', x=xm, y=ym, z=xm*0, hoverinfo='skip', colorscale='Cividis', showlegend=True, showscale=False, reversescale=False, contours={"z": {"show": True, "start": 0.1, "end": 1, "size": 0.1, "width": 1, "color": "white"}})) -# fig.add_trace(go.Mesh3d(name='Joint', x=x, y=y, z=z, intensity=z, colorscale='Viridis')) # can deal with vector xyz, but hover coords not in foreground -fig.add_trace(go.Scatter3d(name='Marginal f(x)', x=xv, y=yv*0+yv[0], mode='lines', z=xv*0, marker_color=col_marginal, showlegend=True, hoverinfo='skip', line={'width': 10})) # , surfaceaxis=2 wait for: https://github.com/plotly/plotly.js/issues/2352 -fig.add_trace(go.Scatter3d(name='Conditional f(x|ŷ)', x=xv, y=yv*0+yv[0], mode='lines', z=xv*0, marker_color=col_conditional, showlegend=True, hoverinfo='skip', line={'width': 4})) # , surfaceaxis=2 wait for: https://github.com/plotly/plotly.js/issues/2352 -fig.add_trace(go.Scatter3d(name='Slice f(x,ŷ)', x=xv, y=yv*0+yv[0], mode='lines', z=xv*0, marker_color=col_slice, showlegend=True, hoverinfo='skip', line={'width': 8})) -fig.update_xaxes(range=rangx, tickmode='array', tickvals=list(range(rangx[0], rangx[1]+1))) -fig.update_yaxes(range=rangy, tickmode='array', tickvals=list(range(rangy[0], rangy[1]+1)), scaleanchor="x", scaleratio=1) -# fig.update_layout(legend=dict(orientation='v', yanchor='top', xanchor='right')) -fig.update_layout(legend=dict(orientation='h', yanchor='bottom', xanchor='right', y=1.02, x=1)) -fig.update_layout(transition_duration=100, transition_easing='linear') -fig.update_scenes(camera_projection_type="orthographic") -fig.update_scenes(aspectmode="cube") -# fig.update_scenes(xaxis_nticks=1) -# fig.update_scenes(yaxis_nticks=1) -fig.update_scenes(zaxis_nticks=1) - -config = { - 'toImageButtonOptions': { - 'format': 'png', # png, svg, pdf, jpeg, webp - 'width': None, # None: use currently-rendered size - 'height': None, - 'filename': 'conditional', - } -} -style = { - 'resize': 'both', - 'overflow': 'auto', - 'width': f'{relwidth}vw', - 'height': f'{relheight}vw' -} - - -layout = dbc.Container( - dbc.Col([ - # Plot - dcc.Graph(id="joint-graph", figure=fig, config=config, style=style), - - html.P(), # style={"margin-bottom": "3cm"} - - # y Slider - dcc.Slider(id="joint-y", min=smin, max=smax, value=randint(smin*10, smax*10)/10, updatemode='drag', marks=None, - tooltip={"template": "ŷ={value}", "placement": "bottom", "always_visible": True}), - - # ρ Slider - dcc.Slider(id="joint-ρ", min=-1, max=1, value=randint(-9, 9)/10, updatemode='mouseup', marks=None, - tooltip={"template": "ρ={value}", "placement": "bottom", "always_visible": True}), - - # Description - dcc.Markdown( - r''' - ## 2D Gaussian - Interactive visualizaton of the 2D Gaussian and its marginal and conditional density. - - $$ - f(\underline x) = \mathcal{N}(\underline x; \underline \mu, \textbf{C}) = - \frac{1}{2\pi \sqrt{\det(\textbf{C})}} - \cdot \exp\!\left\{ -\frac{1}{2} - \cdot (\underline x - \underline \mu)^\top \textbf{C}^{-1} (\underline x - \underline \mu) \right\} \enspace, - \quad \underline{x}\in \mathbb{R}^2 \enspace, \quad \textbf{C} \enspace \text{positive semidefinite} \enspace. - $$ - - ### Formulas and Literature - The Gaussian parameters are restricted to - $$ - \underline \mu = \begin{bmatrix}0 \\ 0\end{bmatrix}\,, \quad - \textbf{C} = \begin{bmatrix}1 & \rho \\ \rho & 1\end{bmatrix} \enspace. - $$ - - Formulas for marginalization and conditioning of are given in the - [[MatrixCookbook](https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf)]. - - Note that the 1D and 2D densities are scaled with respect to each other such that 2D joint and 1D marginal have - the same height and therefore the same shape when looking on the x-z plane. +from components.split_pane import SplitPane +from model.distributions.conditional.conditional import Conditional +from renderer.selfcontained_distribution_renderer import SelfContainedDistributionRenderer as Renderer +dash.register_page(__name__) - ### Interactivity - - GUI - - plot size: initial size from window width; then drag bottom-right corner of graph - - rotate: left mouse click - - pan: right mouse click - - zoom: mouse wheel - - add/remove lines: click in legend - - value in state space (slider) - - value to condition on $\hat{y}$ - - density parameter (slider) - - correlation coefficient $\rho$ - ''', - mathjax=True), - ]), fluid=True, className="g-0") +renderer = Renderer(Conditional()) +options, graph = renderer.get_layout_components() -@callback( - Output("joint-graph", "figure"), - Input("joint-y", "value"), - Input("joint-ρ", "value"), -) -def update(ys, ρ): - patched_fig = Patch() - # Joint Parameters - μ = zeros([2, 1]) - sx = 1 - sy = 1 - # TODO special treatment for singular density - ρ = sign(ρ) * min(abs(ρ), .9999) - C = array([[sx**2, sx*sy*ρ], [sx*sy*ρ, sy**2]]) - # Marginal Parameters - µMarginal = µ[0] - CMarginal = C[0, 0] - marginal_fac = 1 / gauss1(0, 0, CMarginal) - # Density has been modified? - if (callback_context.triggered_id == "joint-ρ") | (callback_context.triggered_id is None): - zMarginal = gauss1(xv, µMarginal, CMarginal) - patched_fig['data'][1]['z'] = zMarginal * marginal_fac - # Compute new joint density values - # TODO should be more elegant than 2 for loops - zJoint = xm*0 - for i in range(xm.shape[0]): - for j in range(xm.shape[1]): - zJoint[i, j] = gauss2(xm[i, j], ym[i, j], μ, C) - zJoint = zJoint / gauss2(0, 0, zeros([2, 1]), C) # rescale to height 1 - patched_fig['data'][0]['z'] = zJoint - # Compute Conditional - # https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf - µCond = µ[0] + C[0, 1] / C[1, 1] * (ys-µ[1]) - CCond = C[0, 0] - C[0, 1] / C[1, 1] * C[0, 1] - zCond = gauss1(xv, µCond, CCond) - zSlice = zCond / gauss1(0, 0, CCond) * gauss1(ys, µ[1], C[1, 1]) / gauss1(0, 0, C[1, 1]) - # Plot Conditional - patched_fig['data'][2]['z'] = zCond * marginal_fac - # Plot Joint Slice - patched_fig['data'][3]['y'] = xv*0+ys - patched_fig['data'][3]['z'] = zSlice + 1e-3 - return patched_fig +layout = SplitPane( + [ + *options + ], + [ + *graph + ], + 30 +) \ No newline at end of file diff --git a/pages/cylinder.py b/pages/cylinder.py new file mode 100644 index 0000000..a3365a5 --- /dev/null +++ b/pages/cylinder.py @@ -0,0 +1,30 @@ +import plotly +import dash +from dash import html, dcc, callback, Input, Output, ALL +from dash_resizable_panels import PanelGroup, Panel, PanelResizeHandle +import dash_bootstrap_components as dbc +import plotly.graph_objects as go +import numpy as np + +from components.split_pane import SplitPane +from model.cylinder.cylinder import Cylinder +from renderer.object_3D_and_2D_renderer import Object3DAnd2DRenderer + + +dash.register_page(__name__) + +cylinder = Cylinder() + +renderer = Object3DAnd2DRenderer(cylinder, "cylinder") +options, graph = renderer.get_layout_components() + +layout = SplitPane( + [ + *options + ], + [ + *graph + ], + 30 + +) \ No newline at end of file diff --git a/pages/gauss1D.py b/pages/gauss1D.py index 8d43629..dcdc742 100644 --- a/pages/gauss1D.py +++ b/pages/gauss1D.py @@ -1,202 +1,20 @@ -import plotly import dash -from dash import dcc, html, Input, Output, callback, Patch -import plotly.graph_objects as go -import dash_bootstrap_components as dbc -from numpy import sqrt, linspace, vstack, pi, nan, full, exp, square, sort, arange, array -from numpy.random import randn, randint -from scipy.special import erfinv -dash.register_page(__name__) - -methods = ['iid', 'Golden-Sequence', 'Equidistant', 'Unscented'] - -config = { - 'toImageButtonOptions': { - 'format': 'svg', # png, svg, pdf, jpeg, webp - 'height': None, # None: use currently-rendered size - 'width': None, - 'filename': 'gauss2d', - }, - 'modeBarButtonsToRemove': ['zoom'], - 'scrollZoom': True, -} - -style = { - 'resize': 'both', - 'overflow': 'auto', -} - - -layout = dbc.Container( - dbc.Col([ - # Plot - dcc.Graph(id="gauss1D-graph", config=config, style=style), - - # Sampling Strategy RadioItems - dbc.RadioItems(id='gauss1D-smethod', - options=[{"label": x, "value": x} for x in methods], - value=methods[randint(len(methods))], - inline=True), - - html.P(), # style={"margin-bottom": "3cm"} - - # param Slider - dcc.Slider(id="gauss1D-p", min=0, max=1, value=randint(3, 7)/10, - tooltip={"template": "p={value}", "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), - - # L Slider - dcc.Slider(id="gauss1D-L", min=0, max=100, step=1, value=randint(5, 25), - tooltip={"template": "L={value}", "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), - - # μ Slider - dcc.Slider(id="gauss1D-μ", min=-5, max=5, step=0.01, value=randint(-20, 20)/10, - tooltip={"template": "µ={value}", "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), - - # σ Slider - dcc.Slider(id="gauss1D-σ", min=0, max=5, step=0.01, value=randint(5, 20)/10, - tooltip={"template": 'σ={value}', "placement": "bottom", "always_visible": True}, updatemode='drag', marks=None), - - # Description - dcc.Markdown( - r''' - ## 1D Gaussian - Interactive visualizaton of the univariate Gaussian density - - $$ - f(x) = \frac{1}{\sqrt{2\pi}\sigma} - \cdot \exp\!\left\{ -\frac{1}{2} \cdot \left(\frac{x-\mu}{\sigma}\right)^2 \right\}\enspace, \quad x\in \mathbb{R} \enspace, - $$ - - with mean $\mu \in \mathbb{R}$ and standard deviation $\sigma \in \mathbb{R}_+$ . - - It was discovered by Karl Friedrich Gauß (1777-1855) in Göttingen, Germany. - - ### Formulas - - quantile function - $Q(p) = \mu + \sigma\, \sqrt{2}\, \mathrm{erf}^{-1}(2p-1)$ - - uniform to Gaussian - $x_i^{\text{Gauss}} = Q(x_i^{\text{uni}})$ - - golden Kronecker sequence - $x_i^{\text{uni}}=\mod( \Phi \cdot (i+z), 1) \enspace, \quad i \in \{1,2,\ldots,L\}\enspace, \quad z \in \mathbb{Z}$ - - equidistant samples - $x_i^{\text{uni}} = \frac{2 i - 1 + \gamma}{2 L} \enspace, \quad i \in \{1,2,\ldots,L\}\enspace,\quad \gamma\in[-1,1]$ - - unscented (𝐿=2) - $x_1=\mu-\sigma\enspace, \quad x_2=\mu+\sigma$ - - unscented (𝐿=3) - TODO - - ### Interactivity - - GUI - - plot size: initial size from window width; then drag bottom-right corner of graph - - add/remove lines: click in legend - - sampling methods (radiobutton) - - independent identically distributed (iid), the usual random samples - - golden sequence, a low-discrepancy Kronecker sequence based on the golden ratio - - equidistant, with identical amount of probability mass for all samples - - unscented transform sampling (𝐿=2) - - sampling parameter (slider) - - iid: dice again - - golden: integer offset 𝑧 - - equidistant: offset 𝛾 - - unscented: TODO - - number of Samples 𝐿 (slider) - - density parameters (slider) - - mean 𝜇 - - standard deviation 𝜎 - ''', - mathjax=True), - - ]), fluid=True, className="g-0") - - -col_density = plotly.colors.qualitative.Plotly[1] -col_samples = plotly.colors.qualitative.Plotly[0] -rang = [-5, 5] - - -@callback( - Output('gauss1D-p', 'min'), - Output('gauss1D-p', 'max'), - Output('gauss1D-p', 'value'), - Output('gauss1D-p', 'step'), - Output('gauss1D-p', 'tooltip'), - Output('gauss1D-L', 'disabled'), - Input("gauss1D-smethod", "value"), -) -def update_smethod(smethod): - patched_tooltip = Patch() - match smethod: - case 'iid': - patched_tooltip.template = "dice" - return 0, 1, .5, 0.001, patched_tooltip, False - case 'Golden-Sequence': - patched_tooltip.template = "z={value}" - return -50, 50, 0, 1, patched_tooltip, False - case 'Equidistant': - patched_tooltip.template = "γ={value}" - return -1, 1, 0, 0.001, patched_tooltip, False - case 'Unscented': - patched_tooltip.template = "{value}" - return 0, 2, 1, 0.001, patched_tooltip, True - case _: - raise Exception("Wrong smethod") - - -@callback( - Output("gauss1D-graph", "figure"), - Input("gauss1D-smethod", "value"), - Input("gauss1D-p", "value"), - Input("gauss1D-L", "value"), - Input("gauss1D-μ", "value"), - Input("gauss1D-σ", "value"), -) -def update(smethod, p, L, μ, σ): - fig = go.Figure() - if σ == 0: - # Dirac Delta - fig.add_trace(go.Scatter(x=[rang[0], μ, μ, μ, rang[1]], y=[0, 0, 1, 0, 0], hoverinfo='skip', line={'width': 5}, name='Dirac Delta', marker_color=col_density, showlegend=True)) - if L > 0: - fig.add_trace(go.Scatter(x=[μ, μ], y=[0, 1], name='Samples', mode='lines', marker_color=col_samples, showlegend=True)) - else: - # Draw Samples - xGauss = None - match smethod: - case 'iid': - # xUni = sort(rand(L)) - xGauss = sort(randn(L)*σ + μ) - case 'Golden-Sequence': - xUni = (sqrt(5)-1)/2 * (arange(L)+1+round(p)) % 1 - case 'Equidistant': - xUni = (2*arange(L)+1+p)/(2*L) - case 'Unscented': - # TODO scaled unscented etc - xGauss = array([μ-σ, μ+σ]) # TODO parameter - case _: - raise Exception("Wrong smethod") - # Transform Samples - if xGauss is None: - xGauss = σ*sqrt(2)*erfinv(2*xUni-1) + μ - L2 = len(xGauss) - sample_height = full([1, L2], gauss1(0, 0, σ)) - # sample_height = full([1, L], 1/L) - # Plot Density - s = linspace(rang[0], rang[1], 500) - # https://plotly.com/python-api-reference/generated/plotly.graph_objects.Scatter.html - # TODO lighter fillcolor: fillcolor=matplotlib.colors.to_rgba('#aabbcc80') - fig.add_trace(go.Scatter(x=s, y=gauss1(s, μ, σ), hoverinfo='skip', line={'width': 5}, line_shape='spline', name='Density', fill='tozeroy', marker_color=col_density, showlegend=True)) - # Plot Samples - xp = vstack((xGauss, xGauss, full([1, L2], nan))).T.flatten() - yp = vstack((full([1, L2], 0), sample_height, full([1, L2], nan))).T.flatten() - fig.add_trace(go.Scatter(x=xp, y=yp, name='Samples', mode='lines', marker_color=col_samples, showlegend=True)) - # Style - fig.update_xaxes(range=rang, tickmode='array', tickvals=list(range(-5, 6))) - fig.update_yaxes(range=[0, None], fixedrange=True) - fig.update_layout(legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)) - fig.update_layout(dragmode="pan") - # fig.update_layout(transition_duration=100, transition_easing='linear') - return fig +from components.split_pane import SplitPane +from model.distributions.gaus1d.gaus1d import Gaus1D +from renderer.selfcontained_distribution_renderer import SelfContainedDistributionRenderer as Renderer +dash.register_page(__name__) -def gauss1(x, μ, σ): - return 1/sqrt(2*pi*σ) * exp(-1/2 * square((x-μ)/σ)) +renderer = Renderer(Gaus1D()) +options, graph = renderer.get_layout_components() + +layout = SplitPane( + [ + *options + ], + [ + *graph + ], + 30 +) \ No newline at end of file diff --git a/pages/gauss2D.py b/pages/gauss2D.py index d914f4e..3000cc4 100644 --- a/pages/gauss2D.py +++ b/pages/gauss2D.py @@ -1,337 +1,20 @@ -import plotly import dash -import pandas -from urllib.error import HTTPError -from dash import dcc, html, Input, Output, callback, Patch -import plotly.graph_objects as go -import dash_bootstrap_components as dbc -from numpy import sqrt, linspace, vstack, hstack, pi, nan, full, exp, square, arange, array, sin, cos, diff, matmul, log10, deg2rad, identity, ones, zeros, diag, cov, mean -from numpy.random import randn, randint -from numpy.linalg import cholesky, eig, det, inv -from scipy.special import erfinv -dash.register_page(__name__) - - -# Samples Library -data_dict = {} - - -# Get Samples from Library (and load if not available) -def get_data(url): - if not (url in data_dict): - try: - data = pandas.read_csv(url, header=None).to_numpy() - except HTTPError: - # URL doesn't exist - data = full([2, 0], nan) - data_dict[url] = data - return data_dict[url] - - -def url_SND_LCD(D, L): - return f'https://raw.githubusercontent.com/KIT-ISAS/deterministic-samples-csv/main/standard-normal/glcd/D{D}-N{L}.csv' - - -# Define Parameters -# Sampling methods -smethods = ['iid', 'Fibonacci', 'LCD', 'SP-Julier04', 'SP-Menegaz11'] -# Transformation methods -tmethods = ['Cholesky', 'Eigendecomposition'] -# Colors -col_density = plotly.colors.qualitative.Plotly[1] -col_samples = plotly.colors.qualitative.Plotly[0] -# axis limits -rangx = [-5, 5] -rangy = [-4, 4] -# plot size relative to window size -relwidth = 95 -relheight = round((relwidth/diff(rangx)*diff(rangy))[0]) -# Gauss ellipse -s = linspace(0, 2*pi, 500) -circ = vstack((cos(s), sin(s))) * 2 - -# Initialize Plot -# https://plotly.com/python-api-reference/generated/plotly.graph_objects.Scatter.html -fig = go.Figure() -fig.add_trace(go.Scatter(name='Density', x=[0], y=[0], mode='lines', marker_color=col_density, showlegend=True, hoverinfo='skip', line={'width': 3}, line_shape='spline', fill='tozerox')) -fig.add_trace(go.Scatter(name='Samples', x=[0], y=[0], mode='markers', marker_color=col_samples, marker_line_color='black', marker_opacity=1, showlegend=True)) -fig.update_xaxes(range=rangx, tickmode='array', tickvals=list(range(rangx[0], rangx[1]+1))) -fig.update_yaxes(range=rangy, tickmode='array', tickvals=list(range(rangy[0], rangy[1]+1)), scaleanchor="x", scaleratio=1) -fig.update_layout(legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)) -# fig.update_layout(transition_duration=100, transition_easing='linear') -fig.update_layout(modebar_add=['drawopenpath', 'eraseshape'], newshape_line_color='cyan', dragmode='pan') - -config = { - 'toImageButtonOptions': { - 'format': 'svg', # png, svg, pdf, jpeg, webp - 'height': None, # None: use currently-rendered size - 'width': None, - 'filename': 'gauss2d', - }, - 'scrollZoom': True, -} - -style = { - 'resize': 'both', - 'overflow': 'auto', - 'width': f'{relwidth}vw', - 'height': f'{relheight}vw', -} - -layout = dbc.Container( - dbc.Col([ - # Plot - dcc.Graph(id="gauss2D-graph", figure=fig, config=config, style=style), - - # Sampling Strategy RadioItems - dbc.RadioItems(id='gauss2D-smethod', - options=[{"label": x, "value": x} for x in smethods], - value=smethods[randint(len(smethods))], - inline=True), - - # Transformation Method RadioItems - dbc.RadioItems(id='gauss2D-tmethod', - options=[{"label": x, "value": x} for x in tmethods], - value=tmethods[randint(len(tmethods))], - inline=True), - - html.P(), # style={"margin-bottom": "3cm"} - - # param Slider - dcc.Slider(id="gauss2D-p", min=0, max=1, value=randint(3, 7)/10, updatemode='drag', marks=None, - tooltip={"template": "p={value}", "placement": "bottom", "always_visible": True}), - - # L Slider - dcc.Slider(id="gauss2D-L", min=log10(1.2), max=4.001, step=0.001, value=2, updatemode='drag', marks=None, # persistence=True, - tooltip={"template": "L={value}", "placement": "bottom", "always_visible": True, "transform": "trafo_L"}), - - # σ Slider - dcc.Slider(id="gauss2D-σx", min=0, max=5, step=0.01, value=1, updatemode='drag', marks=None, - tooltip={"template": 'σx={value}', "placement": "bottom", "always_visible": True}), - dcc.Slider(id="gauss2D-σy", min=0, max=5, step=0.01, value=1, updatemode='drag', marks=None, - tooltip={"template": 'σy={value}', "placement": "bottom", "always_visible": True}), - - # ρ Slider - dcc.Slider(id="gauss2D-ρ", min=-1, max=1, step=0.001, value=0, updatemode='drag', marks=None, - tooltip={"template": 'ρ={value}', "placement": "bottom", "always_visible": True}), - - # Description - dcc.Markdown( - r''' - ## 2D Gaussian - Interactive visualizaton of the bivariate Gaussian density - - $$ - f(\underline x) = \mathcal{N}(\underline x; \underline \mu, \textbf{C}) = - \frac{1}{2\pi \sqrt{\det(\textbf{C})}} - \cdot \exp\!\left\{ -\frac{1}{2} - \cdot (\underline x - \underline \mu)^\top \textbf{C}^{-1} (\underline x - \underline \mu) \right\} \enspace, - \quad \underline{x}\in \mathbb{R}^2 \enspace, \quad \textbf{C} \enspace \text{positive semidefinite} \enspace. - $$ - - ### Formulas and Literature - - quantile function - $Q(p) = \sqrt{2}\, \mathrm{erf}^{-1}(2p-1)$ - - uniform to SND - $\underline x_i^{\text{SND}} = Q(\underline x_i^{\text{uni}})$ - - SND to Gauss: Cholesky - $\underline x_i^{\text{Gauss}} = \mathrm{chol}(\textbf{C}) \cdot \underline x_i^{\text{SND}}$ - - SND to Gauss: Eigendecomposition - [[Frisch23](https://isif.org/media/generalized-fibonacci-grid-low-discrepancy-point-set-optimal-deterministic-gaussian), eq. 18], - [[Frisch21](https://ieeexplore.ieee.org/document/9626975), eq. 4] - $\underline x_i^{\text{Gauss}} = \mathbf{V} \cdot \sqrt{\mathbf{D}} \cdot \underline x_i^{\text{SND}}$ - - Fibonacci-Kronecker Lattice - $\underline x_i^{\text{uni}} = \begin{bmatrix}\mod( \Phi \cdot (i+z), 1) \\ \frac{2 i - 1 + \gamma}{2 L} \end{bmatrix} \enspace, - \quad i \in \{1,2,\ldots,L\}\enspace, \quad z \in \mathbb{Z} \enspace, \quad \gamma\in[-1,1]$ - - LCD: Localized Cumulative Distribution - [[Hanebeck08](https://ieeexplore.ieee.org/document/4648104)], - [[Hanebeck09](https://ieeexplore.ieee.org/document/5400649)], - loaded from [library](https://github.com/KIT-ISAS/deterministic-samples-csv) - $K(\underline x - \underline m, b) = \exp\!\left\{ -\frac{1}{2} \cdot \left\Vert \frac{\underline x - \underline m}{b} \right\Vert_2^2 \right\} \enspace, \quad - F(\underline m, b) = \int_{\mathbb{R}^2} f(\underline x) \, K(\underline x - \underline m, b) \, \mathrm{d} \underline x \enspace,$ - $\widetilde f(x) = \mathcal{N}(\underline x; \underline 0, \textbf{I}) \enspace, \quad - f(\underline x) = \sum_{i=1}^L \delta(\underline x - \underline x_i) \enspace,$ - $D = \int_{\mathbb{R}_+} w(b) \int_{\mathbb{R}^2} \left( \widetilde F(\underline m, b) - F(\underline m, b) \right)^2 \mathrm{d} \underline m \, \mathrm{d} b \enspace,$ - $\left\{\underline x_i^{\text{SND}}\right\}_{i=0}^L = \arg \min_{\underline x_i} \{D\}$ - - SP-Julier14: Sigma Points - [[Julier04](https://ieeexplore.ieee.org/document/1271397), eq. 12] - $L=2\cdot d + 1 \enspace, \quad i\in\{1,2,\dots,d\} \enspace,$ - $\underline x_0=\underline 0 \enspace, \quad W_0 < 1 \enspace,$ - $\underline x_i = \sqrt{\frac{L}{1-W_0}} \cdot \underline e_i \enspace, \quad W_i = \frac{1-W_0}{2 L} \enspace,$ - $\underline x_{i+L} = -x_i \enspace, \quad W_{i+L} = W_i$ - - SP-Menegaz11: Minimum Sigma Set - [[Menegaz11](https://ieeexplore.ieee.org/abstract/document/6161480), eq. 2-8] - $L=d+1 \enspace, \quad i \in \{1,2, \dots d\} \enspace,$ - $0 < w_0 < 1 \enspace, \quad - \alpha=\sqrt{\frac{1-w_0}{d}} \enspace, \quad - C = \sqrt{\mathbf{I} - \alpha^2 \cdot \mathbf 1} \enspace, \quad - \underline x_0 = - \frac{\alpha}{\sqrt{w_0}} \cdot \underline 1$ - $w_{1\colon n} = \mathrm{diag}(w_0 \cdot \alpha^2 \cdot C^{-1} \cdot \mathbf{1} \cdot (C^\top)^{-1}) \enspace,$ - $\underline x_{1\colon n} = C \cdot (\sqrt{\mathbf{I} \cdot w_{1\colon n}})^{-1}$ - ### Interactivity - - GUI - - plot size: initial size from window width; then drag bottom-right corner of graph - - add/remove lines: click in legend - - sampling methods (radiobutton) - - Independent identically distributed (iid), the usual random samples. - - Fibonacci-Kronecker lattice, combination of 1D golden sequence and equidistant. Use with eigendecomposition for best homogeneity. - - LCD SND samples. - - Sigma Points - Julier04. - - transformation methods (radiobutton) - - Cholesky decomposition. - - Eigenvalue-Eigenvector decomposition. - - sampling parameter (slider) - - iid: dice again - - Fibonacci: integer offset 𝑧, offset 𝛾 - - LCD: SND rotation 𝛼, a proxy for dependency on initial guess during optimization - - sigma points: scaling parameter - - number of Samples 𝐿 (slider) - - density parameters (slider) - - standard deviation $\sigma_x$ - - standard deviation $\sigma_y$ - - correlation coefficient $\rho$ - ''', - mathjax=True), - ]), fluid=True, className="g-0") - - -@callback( - Output('gauss2D-p', 'min'), - Output('gauss2D-p', 'max'), - Output('gauss2D-p', 'value'), - Output('gauss2D-p', 'step'), - Output('gauss2D-p', 'tooltip'), - Output('gauss2D-L', 'disabled'), - Input("gauss2D-smethod", "value"), -) -def update_smethod(smethod): - patched_tooltip = Patch() - match smethod: - case 'iid': - patched_tooltip.template = "dice" - # min, max, value, step, tooltip - return 0, 1, .5, 0.001, patched_tooltip, False - case 'Fibonacci': - patched_tooltip.template = "z={value}" - return -50, 50, 0, 1, patched_tooltip, False - case 'LCD': - patched_tooltip.template = "α={value}°" - return -360, 360, 0, 0.1, patched_tooltip, False - case 'SP-Julier04': - patched_tooltip.template = "W₀={value}" - return -2, 1, .1, 0.001, patched_tooltip, True - case 'SP-Menegaz11': - patched_tooltip.template = "Wₙ₊₁={value}" - return 0, 1, 1/3, 0.001, patched_tooltip, True - case _: - raise Exception("Wrong smethod") - - -@callback( - Output("gauss2D-graph", "figure"), - Input("gauss2D-smethod", "value"), - Input("gauss2D-tmethod", "value"), - Input("gauss2D-p", "value"), - Input("gauss2D-L", "value"), - Input("gauss2D-σx", "value"), - Input("gauss2D-σy", "value"), - Input("gauss2D-ρ", "value"), -) -def update(smethod, tmethod, p, L0, σx, σy, ρ): - # Slider Transform, - L = trafo_L(L0) - # Mean - # μ = array([[μx], [μy]]) - μ = array([[0], [0]]) - # Covariance - C = array([[square(σx), σx*σy*ρ], [σx*σy*ρ, square(σy)]]) - C_D, C_R = eig(C) - C_D = C_D[..., None] # to column vector - - patched_fig = Patch() - # Draw SND - weights = None - match smethod: - case 'iid': - xySND = randn(2, L) - case 'Fibonacci': - # TODO 2nd parameter - xUni = (sqrt(5)-1)/2 * (arange(L)+1+round(p)) % 1 - yUni = (2*arange(L)+1)/(2*L) # +p - xyUni = vstack((xUni, yUni)) - xySND = sqrt(2)*erfinv(2*xyUni-1) - case 'LCD': - xySND = get_data(url_SND_LCD(2, L)) - xySND = matmul(rot(p), xySND) - case 'SP-Julier04': - # https://ieeexplore.ieee.org/abstract/document/1271397 - Nx = 2 # dimension - x0 = zeros([Nx, 1]) - W0 = full([1, 1], p) # parameter, W0<1 - x1 = sqrt(Nx/(1-W0) * identity(Nx)) - W1 = full([1, Nx], (1-W0)/(2*Nx)) - x2 = -x1 - W2 = W1 - xySND = hstack((x0, x1, x2)) - weights = hstack((W0, W1, W2)) - case 'SP-Menegaz11': - # https://ieeexplore.ieee.org/abstract/document/6161480 - n = 2 # dimension - w0 = p # parameter, 0=0.3)", "astropy[recommended]", "astropy[typing]", "beautifulsoup4", "bleach", "bottleneck", "certifi", "dask[array]", "fsspec[http] (>=2023.4.0)", "h5py", "html5lib", "ipython (>=4.2)", "jplephem", "mpmath", "pandas", "pre-commit", "pyarrow (>=7.0.0)", "pytest (>=7.0)", "pytz", "s3fs (>=2023.4.0)", "sortedcontainers"] +docs = ["Jinja2 (>=3.1.3)", "astropy[recommended]", "matplotlib (>=3.9.1)", "numpy (<2.0)", "pytest (>=7.0)", "sphinx", "sphinx-astropy[confv2] (>=1.9.1)", "sphinx-changelog (>=1.2.0)", "sphinx_design", "sphinxcontrib-globalsubs (>=0.1.1)", "tomli ; python_version < \"3.11\""] +recommended = ["matplotlib (>=3.5.0,!=3.5.2)", "scipy (>=1.8)"] +test = ["pytest (>=7.0)", "pytest-astropy (>=0.10)", "pytest-astropy-header (>=0.2.1)", "pytest-doctestplus (>=0.12)", "pytest-xdist", "threadpoolctl"] +test-all = ["array-api-strict", "astropy[test]", "coverage[toml]", "ipython (>=4.2)", "objgraph", "sgp4 (>=2.3)", "skyfield (>=1.20)"] +typing = ["typing_extensions (>=4.0.0)"] + +[[package]] +name = "astropy-iers-data" +version = "0.2025.11.3.0.38.37" +description = "IERS Earth Rotation and Leap Second tables for the astropy core package" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "astropy_iers_data-0.2025.11.3.0.38.37-py3-none-any.whl", hash = "sha256:f7e6bf830d1c6d022abaf39739a9dc7e42ea912675113da921967fe9a87d1de4"}, + {file = "astropy_iers_data-0.2025.11.3.0.38.37.tar.gz", hash = "sha256:e018a3efbb6a4e4ea5a5a2a73bb3575b0123f3643b415a7327ec1dbd169787dd"}, +] + +[package.extras] +docs = ["pytest"] +test = ["hypothesis", "pytest", "pytest-remotedata"] + +[[package]] +name = "attrs" +version = "25.4.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, +] + +[[package]] +name = "beartype" +version = "0.22.5" +description = "Unbearably fast near-real-time pure-Python runtime-static type-checker." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "beartype-0.22.5-py3-none-any.whl", hash = "sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0"}, + {file = "beartype-0.22.5.tar.gz", hash = "sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341"}, +] + +[package.extras] +dev = ["autoapi (>=0.9.0)", "celery", "click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "fastmcp ; python_version > \"3.9.0\" and python_version < \"3.14.0\"", "jax[cpu] ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "jaxtyping ; sys_platform == \"linux\"", "langchain ; python_version < \"3.14.0\" and sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "numba ; python_version < \"3.14.0\"", "numpy ; python_version < \"3.14.0\" and sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera (>=0.26.0) ; python_version < \"3.14.0\"", "poetry", "polars ; python_version < \"3.14.0\"", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=6.2.0)", "redis", "rich-click", "setuptools", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "sqlalchemy", "torch ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "tox (>=3.20.1)", "typer", "typing-extensions (>=3.10.0.0)", "xarray ; python_version < \"3.14.0\""] +doc-ghp = ["mkdocs-material[imaging] (>=9.6.0)", "mkdocstrings-python (>=1.16.0)", "mkdocstrings-python-xref (>=1.16.0)"] +doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "setuptools", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] +test = ["celery", "click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "fastmcp ; python_version > \"3.9.0\" and python_version < \"3.14.0\"", "jax[cpu] ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "jaxtyping ; sys_platform == \"linux\"", "langchain ; python_version < \"3.14.0\" and sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "numba ; python_version < \"3.14.0\"", "numpy ; python_version < \"3.14.0\" and sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera (>=0.26.0) ; python_version < \"3.14.0\"", "poetry", "polars ; python_version < \"3.14.0\"", "pygments", "pyright (>=1.1.370)", "pytest (>=6.2.0)", "redis", "rich-click", "sphinx", "sqlalchemy", "torch ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "tox (>=3.20.1)", "typer", "typing-extensions (>=3.10.0.0)", "xarray ; python_version < \"3.14.0\""] +test-tox = ["celery", "click", "equinox ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "fastmcp ; python_version > \"3.9.0\" and python_version < \"3.14.0\"", "jax[cpu] ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "jaxtyping ; sys_platform == \"linux\"", "langchain ; python_version < \"3.14.0\" and sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "numba ; python_version < \"3.14.0\"", "numpy ; python_version < \"3.14.0\" and sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera (>=0.26.0) ; python_version < \"3.14.0\"", "poetry", "polars ; python_version < \"3.14.0\"", "pygments", "pyright (>=1.1.370)", "pytest (>=6.2.0)", "redis", "rich-click", "sphinx", "sqlalchemy", "torch ; sys_platform == \"linux\" and python_version < \"3.14.0\"", "typer", "typing-extensions (>=3.10.0.0)", "xarray ; python_version < \"3.14.0\""] +test-tox-coverage = ["coverage (>=5.5)"] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["dev"] +files = [ + {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, + {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "blinker" +version = "1.9.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, +] + +[[package]] +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\" or os_name == \"nt\" and implementation_name != \"pypy\"" +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, +] + +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + +[[package]] +name = "choreographer" +version = "1.2.1" +description = "Devtools Protocol implementation for chrome." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "choreographer-1.2.1-py3-none-any.whl", hash = "sha256:9af5385effa3c204dbc337abf7ac74fd8908ced326a15645dc31dde75718c77e"}, + {file = "choreographer-1.2.1.tar.gz", hash = "sha256:022afd72b1e9b0bcb950420b134e70055a294c791b6f36cfb47d89745b701b5f"}, +] + +[package.dependencies] +logistro = ">=2.0.1" +simplejson = ">=3.19.3" + +[[package]] +name = "click" +version = "8.3.0" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +files = [ + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} + +[[package]] +name = "contourpy" +version = "1.3.2" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, + {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2"}, + {file = "contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0"}, + {file = "contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd"}, + {file = "contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f"}, + {file = "contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e"}, + {file = "contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912"}, + {file = "contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef"}, + {file = "contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f"}, + {file = "contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd"}, + {file = "contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1"}, + {file = "contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5"}, + {file = "contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.15.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "cryptography" +version = "46.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["dev"] +files = [ + {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926"}, + {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71"}, + {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac"}, + {file = "cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018"}, + {file = "cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb"}, + {file = "cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c"}, + {file = "cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3"}, + {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20"}, + {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de"}, + {file = "cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914"}, + {file = "cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db"}, + {file = "cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21"}, + {file = "cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506"}, + {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963"}, + {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4"}, + {file = "cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df"}, + {file = "cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f"}, + {file = "cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372"}, + {file = "cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32"}, + {file = "cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}, + {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}, +] + +[package.dependencies] +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} +typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "dash" +version = "2.18.2" +description = "A Python framework for building reactive web-apps. Developed by Plotly." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "dash-2.18.2-py3-none-any.whl", hash = "sha256:0ce0479d1bc958e934630e2de7023b8a4558f23ce1f9f5a4b34b65eb3903a869"}, + {file = "dash-2.18.2.tar.gz", hash = "sha256:20e8404f73d0fe88ce2eae33c25bbc513cbe52f30d23a401fa5f24dbb44296c8"}, +] + +[package.dependencies] +beautifulsoup4 = {version = ">=4.8.2", optional = true, markers = "extra == \"testing\""} +cryptography = {version = "*", optional = true, markers = "extra == \"testing\""} +dash-core-components = "2.0.0" +dash-html-components = "2.0.0" +dash-table = "5.0.0" +dash-testing-stub = {version = ">=0.0.2", optional = true, markers = "extra == \"testing\""} +Flask = ">=1.0.4,<3.1" +importlib-metadata = "*" +lxml = {version = ">=4.6.2", optional = true, markers = "extra == \"testing\""} +multiprocess = {version = ">=0.70.12", optional = true, markers = "extra == \"testing\""} +nest-asyncio = "*" +percy = {version = ">=2.0.2", optional = true, markers = "extra == \"testing\""} +plotly = ">=5.0.0" +psutil = {version = ">=5.8.0", optional = true, markers = "extra == \"testing\""} +pytest = {version = ">=6.0.2", optional = true, markers = "extra == \"testing\""} +requests = [ + {version = "*"}, + {version = ">=2.21.0", extras = ["security"], optional = true, markers = "extra == \"testing\""}, +] +retrying = "*" +selenium = {version = ">=3.141.0,<=4.2.0", optional = true, markers = "extra == \"testing\""} +setuptools = "*" +typing-extensions = ">=4.1.1" +waitress = {version = ">=1.4.4", optional = true, markers = "extra == \"testing\""} +Werkzeug = "<3.1" + +[package.extras] +celery = ["celery[redis] (>=5.1.2)", "redis (>=3.5.3)"] +ci = ["black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==7.0.0)", "flaky (==3.8.1)", "flask-talisman (==1.0.0)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.26.3)", "openpyxl", "orjson (==3.10.3)", "pandas (>=1.4.0)", "pyarrow", "pylint (==3.0.3)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "pyzmq (==25.1.2)", "xlrd (>=2.0.1)"] +compress = ["flask-compress"] +dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"] +diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] +testing = ["beautifulsoup4 (>=4.8.2)", "cryptography", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"] + +[[package]] +name = "dash-bootstrap-components" +version = "1.7.1" +description = "Bootstrap themed components for use in Plotly Dash" +optional = false +python-versions = "<4,>=3.9" +groups = ["main"] +files = [ + {file = "dash_bootstrap_components-1.7.1-py3-none-any.whl", hash = "sha256:5e8eae7ee1d013f69e272c68c1015b53ab71802460152088f33fffa90d245199"}, + {file = "dash_bootstrap_components-1.7.1.tar.gz", hash = "sha256:30d48340d6dc89831d6c06e400cd4236f0d5363562c05b2a922f21545695a082"}, +] + +[package.dependencies] +dash = ">=2.0.0" + +[package.extras] +pandas = ["numpy (>=2.0.2)", "pandas (>=2.2.3)"] + +[[package]] +name = "dash-core-components" +version = "2.0.0" +description = "Core component suite for Dash" +optional = false +python-versions = "*" +groups = ["main", "dev"] +files = [ + {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, + {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, +] + +[[package]] +name = "dash-html-components" +version = "2.0.0" +description = "Vanilla HTML components for Dash" +optional = false +python-versions = "*" +groups = ["main", "dev"] +files = [ + {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, + {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, +] + +[[package]] +name = "dash-mantine-components" +version = "0.14.7" +description = "Plotly Dash Components based on Mantine" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "dash_mantine_components-0.14.7-py3-none-any.whl", hash = "sha256:be8e96abff5413852afbba497577892f931f93d686b375f35d180ffb4e1e1c4b"}, + {file = "dash_mantine_components-0.14.7.tar.gz", hash = "sha256:8157a98aa294406fa2ac884407b59a5a833092804e511a8fe782f3ee978c9b44"}, +] + +[package.dependencies] +dash = ">=2" + +[package.extras] +dev = ["black", "build", "dash[ci,dev,testing] (>=2.0)", "pytest (<8.1.0)", "pyyaml (>=5.0)", "selenium (<4.3.0)", "wheel"] + +[[package]] +name = "dash-resizable-panels" +version = "0.1.0" +description = "Dash component for resizable panel groups/layouts" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "dash_resizable_panels-0.1.0-py3-none-any.whl", hash = "sha256:d13a2ab809415c132266698bc4f0f22221f2202fa2c37bd1b6fce7158f334636"}, + {file = "dash_resizable_panels-0.1.0.tar.gz", hash = "sha256:e93243052168e8ba21fe09e5ffd7a0366de21aeab694122e2365ef156e2f8a57"}, +] + +[[package]] +name = "dash-table" +version = "5.0.0" +description = "Dash table" +optional = false +python-versions = "*" +groups = ["main", "dev"] +files = [ + {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, + {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, +] + +[[package]] +name = "dash-testing-stub" +version = "0.0.2" +description = "Package installed with dash[testing] for optional loading of pytest dash plugin." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "dash-testing-stub-0.0.2.tar.gz", hash = "sha256:0a98f7da9fe41dd3a37d781bc1d5672319448fdf98e47fd867aff2123171a357"}, + {file = "dash_testing_stub-0.0.2-py3-none-any.whl", hash = "sha256:a44d530a77e1ede9c6528be4b5951f34c6109b419a09f2691422375ffa7d09de"}, +] + +[[package]] +name = "dash-vtk" +version = "0.0.9" +description = "React based declarative usage of vtk.js for Dash" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "dash_vtk-0.0.9-py3-none-any.whl", hash = "sha256:107a9df32c72acf681b265c454f315879eb6f1bac9da37d4792d3fe1ba21caf7"}, + {file = "dash_vtk-0.0.9.tar.gz", hash = "sha256:27e9b944963013aa5d52db38c8e72f5d315e59084902d35b2708080ce09ee578"}, +] + +[package.dependencies] +dash = "*" +vtk = "*" + +[[package]] +name = "deterministic-gaussian-sampling-fibonacci" +version = "0.2.4" +description = "Deterministic sampling via orthogonal inverse transform of low-discrepancy Fibonacci grids." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +h5py = ">=3.15.1,<4.0.0" +numpy = ">=2.0.0,<3.0.0" +scipy = {version = ">=1.15,<1.16", markers = "python_version < \"3.11\""} + +[package.source] +type = "git" +url = "https://github.com/KIT-ISAS/deterministic_gaussian_sampling_fibonacci" +reference = "HEAD" +resolved_reference = "fa97bc2793e87424fe56e208a5932a6cd6ec8ea2" + +[[package]] +name = "dill" +version = "0.4.0" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, + {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, + {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filterpy" +version = "1.4.5" +description = "Kalman filtering and optimal estimation library" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "filterpy-1.4.5.zip", hash = "sha256:4f2a4d39e4ea601b9ab42b2db08b5918a9538c168cff1c6895ae26646f3d73b1"}, +] + +[package.dependencies] +matplotlib = "*" +numpy = "*" +scipy = "*" + +[[package]] +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "fonttools" +version = "4.60.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"}, + {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"}, + {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"}, + {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"}, + {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"}, + {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"}, + {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"}, + {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"}, + {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"}, + {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"}, + {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"}, + {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"}, + {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"}, + {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"}, + {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"}, + {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"}, + {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + +[[package]] +name = "gunicorn" +version = "23.0.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, + {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "h5py" +version = "3.15.1" +description = "Read and write HDF5 files from Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "h5py-3.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67e59f6c2f19a32973a40f43d9a088ae324fe228c8366e25ebc57ceebf093a6b"}, + {file = "h5py-3.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e2f471688402c3404fa4e13466e373e622fd4b74b47b56cfdff7cc688209422"}, + {file = "h5py-3.15.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c45802bcb711e128a6839cb6c01e9ac648dc55df045c9542a675c771f15c8d5"}, + {file = "h5py-3.15.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64ce3f6470adb87c06e3a8dd1b90e973699f1759ad79bfa70c230939bff356c9"}, + {file = "h5py-3.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4411c1867b9899a25e983fff56d820a66f52ac326bbe10c7cdf7d832c9dcd883"}, + {file = "h5py-3.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2cbc4104d3d4aca9d6db8c0c694555e255805bfeacf9eb1349bda871e26cacbe"}, + {file = "h5py-3.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:01f55111ca516f5568ae7a7fc8247dfce607de331b4467ee8a9a6ed14e5422c7"}, + {file = "h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243"}, + {file = "h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509"}, + {file = "h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b"}, + {file = "h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf"}, + {file = "h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c"}, + {file = "h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831"}, + {file = "h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878"}, + {file = "h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427"}, + {file = "h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c"}, + {file = "h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76"}, + {file = "h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e"}, + {file = "h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce"}, + {file = "h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171"}, + {file = "h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a"}, + {file = "h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e"}, + {file = "h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652"}, + {file = "h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5"}, + {file = "h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123"}, + {file = "h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5"}, + {file = "h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8"}, + {file = "h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599"}, + {file = "h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0"}, + {file = "h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52"}, + {file = "h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97"}, + {file = "h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763"}, + {file = "h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2"}, + {file = "h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a"}, + {file = "h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e"}, + {file = "h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13"}, + {file = "h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa"}, + {file = "h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734"}, + {file = "h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf"}, + {file = "h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69"}, +] + +[package.dependencies] +numpy = ">=1.21.2" + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "kaleido" +version = "1.2.0" +description = "Plotly graph export library" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "kaleido-1.2.0-py3-none-any.whl", hash = "sha256:c27ed82b51df6b923d0e656feac221343a0dbcd2fb9bc7e6b1db97f61e9a1513"}, + {file = "kaleido-1.2.0.tar.gz", hash = "sha256:fa621a14423e8effa2895a2526be00af0cf21655be1b74b7e382c171d12e71ef"}, +] + +[package.dependencies] +choreographer = ">=1.1.1" +logistro = ">=1.0.8" +orjson = ">=3.10.15" +packaging = "*" +pytest-timeout = ">=2.4.0" + +[[package]] +name = "kent-distribution" +version = "0.1.0" +description = "Implements calculation of the density and fitting (using maximum likelihood estimate) of the Kent distribution. A unittest is performed if kent_distribution.py is called from the command line." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +matplotlib = "*" +numpy = "*" +scipy = "*" + +[package.source] +type = "git" +url = "https://github.com/Vlad-Kor/kent_distribution" +reference = "HEAD" +resolved_reference = "1637440397d97c51ad688fdcc8e2c96c6db48d77" + +[[package]] +name = "kiwisolver" +version = "1.4.9" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, + {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, +] + +[[package]] +name = "logistro" +version = "2.0.1" +description = "Simple wrapper over logging for a couple basic features" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "logistro-2.0.1-py3-none-any.whl", hash = "sha256:06ffa127b9fb4ac8b1972ae6b2a9d7fde57598bf5939cd708f43ec5bba2d31eb"}, + {file = "logistro-2.0.1.tar.gz", hash = "sha256:8446affc82bab2577eb02bfcbcae196ae03129287557287b6a070f70c1985047"}, +] + +[[package]] +name = "lxml" +version = "6.0.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"}, + {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"}, + {file = "lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"}, + {file = "lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"}, + {file = "lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"}, + {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"}, + {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"}, + {file = "lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"}, + {file = "lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"}, + {file = "lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"}, + {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"}, + {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"}, + {file = "lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"}, + {file = "lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"}, + {file = "lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"}, + {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"}, + {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"}, + {file = "lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"}, + {file = "lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"}, + {file = "lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"}, + {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"}, + {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"}, + {file = "lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"}, + {file = "lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"}, + {file = "lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"}, + {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"}, + {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"}, + {file = "lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"}, + {file = "lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"}, + {file = "lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"}, + {file = "lxml-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62"}, + {file = "lxml-6.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f"}, + {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7"}, + {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3"}, + {file = "lxml-6.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac"}, + {file = "lxml-6.0.2-cp38-cp38-win32.whl", hash = "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512"}, + {file = "lxml-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca"}, + {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694"}, + {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb"}, + {file = "lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f"}, + {file = "lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304"}, + {file = "lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"}, + {file = "lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] + +[[package]] +name = "markupsafe" +version = "3.0.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + +[[package]] +name = "matplotlib" +version = "3.10.7" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380"}, + {file = "matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d"}, + {file = "matplotlib-3.10.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297"}, + {file = "matplotlib-3.10.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42"}, + {file = "matplotlib-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7"}, + {file = "matplotlib-3.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3"}, + {file = "matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a"}, + {file = "matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6"}, + {file = "matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a"}, + {file = "matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1"}, + {file = "matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc"}, + {file = "matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e"}, + {file = "matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9"}, + {file = "matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748"}, + {file = "matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f"}, + {file = "matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0"}, + {file = "matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695"}, + {file = "matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65"}, + {file = "matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee"}, + {file = "matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8"}, + {file = "matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f"}, + {file = "matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c"}, + {file = "matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1"}, + {file = "matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632"}, + {file = "matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84"}, + {file = "matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815"}, + {file = "matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7"}, + {file = "matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355"}, + {file = "matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b"}, + {file = "matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67"}, + {file = "matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67"}, + {file = "matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84"}, + {file = "matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2"}, + {file = "matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf"}, + {file = "matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100"}, + {file = "matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f"}, + {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715"}, + {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1"}, + {file = "matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722"}, + {file = "matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866"}, + {file = "matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb"}, + {file = "matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1"}, + {file = "matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4"}, + {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318"}, + {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca"}, + {file = "matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc"}, + {file = "matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8"}, + {file = "matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91"}, + {file = "matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=3" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "multiprocess" +version = "0.70.18" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "multiprocess-0.70.18-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25d4012dcaaf66b9e8e955f58482b42910c2ee526d532844d8bcf661bbc604df"}, + {file = "multiprocess-0.70.18-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:06b19433de0d02afe5869aec8931dd5c01d99074664f806c73896b0d9e527213"}, + {file = "multiprocess-0.70.18-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6fa1366f994373aaf2d4738b0f56e707caeaa05486e97a7f71ee0853823180c2"}, + {file = "multiprocess-0.70.18-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b8940ae30139e04b076da6c5b83e9398585ebdf0f2ad3250673fef5b2ff06d6"}, + {file = "multiprocess-0.70.18-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0929ba95831adb938edbd5fb801ac45e705ecad9d100b3e653946b7716cb6bd3"}, + {file = "multiprocess-0.70.18-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d77f8e4bfe6c6e2e661925bbf9aed4d5ade9a1c6502d5dfc10129b9d1141797"}, + {file = "multiprocess-0.70.18-pp38-pypy38_pp73-macosx_10_9_arm64.whl", hash = "sha256:2dbaae9bffa1fb2d58077c0044ffe87a8c8974e90fcf778cdf90e139c970d42a"}, + {file = "multiprocess-0.70.18-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcac5a4e81f1554d98d1bba963eeb1bd24966432f04fcbd29b6e1a16251ad712"}, + {file = "multiprocess-0.70.18-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c0c7cd75d0987ab6166d64e654787c781dbacbcbcaaede4c1ffe664720b3e14b"}, + {file = "multiprocess-0.70.18-pp39-pypy39_pp73-macosx_10_13_arm64.whl", hash = "sha256:9fd8d662f7524a95a1be7cbea271f0b33089fe792baabec17d93103d368907da"}, + {file = "multiprocess-0.70.18-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:3fbba48bfcd932747c33f0b152b26207c4e0840c35cab359afaff7a8672b1031"}, + {file = "multiprocess-0.70.18-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5f9be0342e597dde86152c10442c5fb6c07994b1c29de441b7a3a08b0e6be2a0"}, + {file = "multiprocess-0.70.18-py310-none-any.whl", hash = "sha256:60c194974c31784019c1f459d984e8f33ee48f10fcf42c309ba97b30d9bd53ea"}, + {file = "multiprocess-0.70.18-py311-none-any.whl", hash = "sha256:5aa6eef98e691281b3ad923be2832bf1c55dd2c859acd73e5ec53a66aae06a1d"}, + {file = "multiprocess-0.70.18-py312-none-any.whl", hash = "sha256:9b78f8e5024b573730bfb654783a13800c2c0f2dfc0c25e70b40d184d64adaa2"}, + {file = "multiprocess-0.70.18-py313-none-any.whl", hash = "sha256:871743755f43ef57d7910a38433cfe41319e72be1bbd90b79c7a5ac523eb9334"}, + {file = "multiprocess-0.70.18-py38-none-any.whl", hash = "sha256:dbf705e52a154fe5e90fb17b38f02556169557c2dd8bb084f2e06c2784d8279b"}, + {file = "multiprocess-0.70.18-py39-none-any.whl", hash = "sha256:e78ca805a72b1b810c690b6b4cc32579eba34f403094bbbae962b7b5bf9dfcb8"}, + {file = "multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d"}, +] + +[package.dependencies] +dill = ">=0.4.0" + +[[package]] +name = "narwhals" +version = "2.10.2" +description = "Extremely lightweight compatibility layer between dataframe libraries" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "narwhals-2.10.2-py3-none-any.whl", hash = "sha256:059cd5c6751161b97baedcaf17a514c972af6a70f36a89af17de1a0caf519c43"}, + {file = "narwhals-2.10.2.tar.gz", hash = "sha256:ff738a08bc993cbb792266bec15346c1d85cc68fdfe82a23283c3713f78bd354"}, +] + +[package.extras] +cudf = ["cudf (>=24.10.0)"] +dask = ["dask[dataframe] (>=2024.8)"] +duckdb = ["duckdb (>=1.1)"] +ibis = ["ibis-framework (>=6.0.0)", "packaging", "pyarrow-hotfix", "rich"] +modin = ["modin"] +pandas = ["pandas (>=1.1.3)"] +polars = ["polars (>=0.20.4)"] +pyarrow = ["pyarrow (>=13.0.0)"] +pyspark = ["pyspark (>=3.5.0)"] +pyspark-connect = ["pyspark[connect] (>=3.5.0)"] +sqlframe = ["sqlframe (>=3.22.0,!=3.39.3)"] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["main", "dev"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "numpy" +version = "2.2.6" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, + {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, + {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, + {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, + {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, + {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, + {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, + {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, + {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, + {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, + {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, + {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, +] + +[[package]] +name = "numpy-quaternion" +version = "2024.0.12" +description = "Add a quaternion dtype to NumPy" +optional = false +python-versions = "<3.14,>=3.10" +groups = ["main"] +files = [ + {file = "numpy_quaternion-2024.0.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:606b0fb2d58a0b15419432a3714020b8c60fc6a829d9d1e962ae21c769e95dad"}, + {file = "numpy_quaternion-2024.0.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27d8ffaa871ba67ca599008692926bb45b964f1eedd83021699a137a0d510a7f"}, + {file = "numpy_quaternion-2024.0.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48c61c0feb952347486816a0fe5d2712a1f57a427ed28cf845c570135d592c3"}, + {file = "numpy_quaternion-2024.0.12-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:536f8448c26d9af2747fc10f7ed8f9b308c840afbae14c617a1af9e02e5eb3ed"}, + {file = "numpy_quaternion-2024.0.12-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76de6b14aaf5a2578bc03d7d2118235b8c668cc3aeab4fdc48fda1f189f31f1c"}, + {file = "numpy_quaternion-2024.0.12-cp310-cp310-win_amd64.whl", hash = "sha256:f862f0a1a9ad60966fdc37ee61c233da105e6776da70da81c010adc082743798"}, + {file = "numpy_quaternion-2024.0.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19e6ba157bc15d48f8d6f8be25f200d3a0858ae655e54a30bd423ca0d3aa5659"}, + {file = "numpy_quaternion-2024.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb8087c737422ff893163782dd99188e159aac5dcddec83538ad53a172755d3d"}, + {file = "numpy_quaternion-2024.0.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa37c4102f47dc5e675dd6714f390edcc6a9c3151a427d34bd5778e71f26d489"}, + {file = "numpy_quaternion-2024.0.12-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16f797e1733d04b48f3ad86b4e38398d49415f28a9d7d55b5e541533c3badddf"}, + {file = "numpy_quaternion-2024.0.12-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989e6edf66b8b083354b24e7303d405ed9cd76d29b37852474794c8b2cab5b38"}, + {file = "numpy_quaternion-2024.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:63b959ed309dbd0e8da2e41f2574b4b8f3d8f972bf3c49fdc54de56cdf28687f"}, + {file = "numpy_quaternion-2024.0.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7c96c1faac4d6c6ab2505beb26a82f2e8b3d3470d72c92af1837bc8f883378e"}, + {file = "numpy_quaternion-2024.0.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:947a5afbf1f42f6a86c5ea288165631bde3eb243fcb50e50af611149863bb541"}, + {file = "numpy_quaternion-2024.0.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:28cbece557ab04b393b1864f8790d1b4482e49cbc98566549ec3490e5af4d8db"}, + {file = "numpy_quaternion-2024.0.12-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8257cad3b9586d04e4c8f9a2d182b72a35e068d0e787dd5560838275be87537"}, + {file = "numpy_quaternion-2024.0.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a993241d7f885654c5496db2f581ff86f4cb6a298671419956223cab3535c2"}, + {file = "numpy_quaternion-2024.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:aea04cc3d01676fca03ca23b7c723a191d31af3cff853ddc47a2422647a03939"}, + {file = "numpy_quaternion-2024.0.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:66d3072b9f01b853b32ea99670bb739631aef36ac2317fd845e90b2c887ebd6c"}, + {file = "numpy_quaternion-2024.0.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7c302a828b80d2553be50bbde978ed0b6c1dfc7be310afaa083acc75a2e6b3"}, + {file = "numpy_quaternion-2024.0.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e54e41ca9991832aa0303c8956151f1933fc15b5645ef5b8fb6b93356ee30b8b"}, + {file = "numpy_quaternion-2024.0.12-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f947b8fdccb1cfe6d1157578477741e0c51ba1bb4d43358c5fbb7b26183942fc"}, + {file = "numpy_quaternion-2024.0.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02503230d3e5678e955ad1a64cd1738ff2739b9a1cd3fe58d5bc98fd491908f"}, + {file = "numpy_quaternion-2024.0.12-cp313-cp313-win_amd64.whl", hash = "sha256:9ce0fa7af5075b0b0c39bb1ac80fb3fb8b9c5ae3c2140672933391b295f84210"}, + {file = "numpy_quaternion-2024.0.12.tar.gz", hash = "sha256:5ecb4e310e732bc21687474c1bc6cd6187d5c22da78d342379fe81f12f46037f"}, +] + +[package.dependencies] +numpy = ">=1.25,<3" +scipy = ">=1.5,<2" + +[package.extras] +docs = ["mkdocs-material", "mkdocstrings-python", "pymdown-extensions"] + +[[package]] +name = "openpyxl" +version = "3.1.5" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, + {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "orjson" +version = "3.11.4" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "orjson-3.11.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e3aa2118a3ece0d25489cbe48498de8a5d580e42e8d9979f65bf47900a15aba1"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a69ab657a4e6733133a3dca82768f2f8b884043714e8d2b9ba9f52b6efef5c44"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3740bffd9816fc0326ddc406098a3a8f387e42223f5f455f2a02a9f834ead80c"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65fd2f5730b1bf7f350c6dc896173d3460d235c4be007af73986d7cd9a2acd23"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fdc3ae730541086158d549c97852e2eea6820665d4faf0f41bf99df41bc11ea"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e10b4d65901da88845516ce9f7f9736f9638d19a1d483b3883dc0182e6e5edba"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6a03a678085f64b97f9d4a9ae69376ce91a3a9e9b56a82b1580d8e1d501aff"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c82e4f0b1c712477317434761fbc28b044c838b6b1240d895607441412371ac"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d58c166a18f44cc9e2bad03a327dc2d1a3d2e85b847133cfbafd6bfc6719bd79"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94f206766bf1ea30e1382e4890f763bd1eefddc580e08fec1ccdc20ddd95c827"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:41bf25fb39a34cf8edb4398818523277ee7096689db352036a9e8437f2f3ee6b"}, + {file = "orjson-3.11.4-cp310-cp310-win32.whl", hash = "sha256:fa9627eba4e82f99ca6d29bc967f09aba446ee2b5a1ea728949ede73d313f5d3"}, + {file = "orjson-3.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:23ef7abc7fca96632d8174ac115e668c1e931b8fe4dde586e92a500bf1914dcc"}, + {file = "orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39"}, + {file = "orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907"}, + {file = "orjson-3.11.4-cp311-cp311-win32.whl", hash = "sha256:87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c"}, + {file = "orjson-3.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a"}, + {file = "orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045"}, + {file = "orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50"}, + {file = "orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9"}, + {file = "orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa"}, + {file = "orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140"}, + {file = "orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e"}, + {file = "orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534"}, + {file = "orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6"}, + {file = "orjson-3.11.4-cp313-cp313-win32.whl", hash = "sha256:6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839"}, + {file = "orjson-3.11.4-cp313-cp313-win_amd64.whl", hash = "sha256:09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a"}, + {file = "orjson-3.11.4-cp313-cp313-win_arm64.whl", hash = "sha256:a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de"}, + {file = "orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:42d43a1f552be1a112af0b21c10a5f553983c2a0938d2bbb8ecd8bc9fb572803"}, + {file = "orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:26a20f3fbc6c7ff2cb8e89c4c5897762c9d88cf37330c6a117312365d6781d54"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3f20be9048941c7ffa8fc523ccbd17f82e24df1549d1d1fe9317712d19938e"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aac364c758dc87a52e68e349924d7e4ded348dedff553889e4d9f22f74785316"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5c54a6d76e3d741dcc3f2707f8eeb9ba2a791d3adbf18f900219b62942803b1"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28485bdca8617b79d44627f5fb04336897041dfd9fa66d383a49d09d86798bc"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfc2a484cad3585e4ba61985a6062a4c2ed5c7925db6d39f1fa267c9d166487f"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e34dbd508cb91c54f9c9788923daca129fe5b55c5b4eebe713bf5ed3791280cf"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b13c478fa413d4b4ee606ec8e11c3b2e52683a640b006bb586b3041c2ca5f606"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:724ca721ecc8a831b319dcd72cfa370cc380db0bf94537f08f7edd0a7d4e1780"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:977c393f2e44845ce1b540e19a786e9643221b3323dae190668a98672d43fb23"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e539e382cf46edec157ad66b0b0872a90d829a6b71f17cb633d6c160a223155"}, + {file = "orjson-3.11.4-cp314-cp314-win32.whl", hash = "sha256:d63076d625babab9db5e7836118bdfa086e60f37d8a174194ae720161eb12394"}, + {file = "orjson-3.11.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a54d6635fa3aaa438ae32e8570b9f0de36f3f6562c308d2a2a452e8b0592db1"}, + {file = "orjson-3.11.4-cp314-cp314-win_arm64.whl", hash = "sha256:78b999999039db3cf58f6d230f524f04f75f129ba3d1ca2ed121f8657e575d3d"}, + {file = "orjson-3.11.4-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:405261b0a8c62bcbd8e2931c26fdc08714faf7025f45531541e2b29e544b545b"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af02ff34059ee9199a3546f123a6ab4c86caf1708c79042caf0820dc290a6d4f"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b2eba969ea4203c177c7b38b36c69519e6067ee68c34dc37081fac74c796e10"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0baa0ea43cfa5b008a28d3c07705cf3ada40e5d347f0f44994a64b1b7b4b5350"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80fd082f5dcc0e94657c144f1b2a3a6479c44ad50be216cf0c244e567f5eae19"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3704d35e47d5bee811fb1cbd8599f0b4009b14d451c4c57be5a7e25eb89a13"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa447f2b5356779d914658519c874cf3b7629e99e63391ed519c28c8aea4919"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bba5118143373a86f91dadb8df41d9457498226698ebdf8e11cbb54d5b0e802d"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:622463ab81d19ef3e06868b576551587de8e4d518892d1afab71e0fbc1f9cffc"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3e0a700c4b82144b72946b6629968df9762552ee1344bfdb767fecdd634fbd5a"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6e18a5c15e764e5f3fc569b47872450b4bcea24f2a6354c0a0e95ad21045d5a9"}, + {file = "orjson-3.11.4-cp39-cp39-win32.whl", hash = "sha256:fb1c37c71cad991ef4d89c7a634b5ffb4447dbd7ae3ae13e8f5ee7f1775e7ab1"}, + {file = "orjson-3.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:e2985ce8b8c42d00492d0ed79f2bd2b6460d00f2fa671dfde4bf2e02f49bf5c6"}, + {file = "orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d"}, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +description = "Capture the outcome of Python function calls." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, + {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pandas" +version = "2.3.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, +] + +[package.dependencies] +numpy = {version = ">=1.22.4", markers = "python_version < \"3.11\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "percy" +version = "2.0.2" +description = "Python client library for visual regression testing with Percy (https://percy.io)." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "percy-2.0.2-py2.py3-none-any.whl", hash = "sha256:c1647b768810e9453220a7721a5d52cec560dee913d13c1e29b713703f4f223e"}, + {file = "percy-2.0.2.tar.gz", hash = "sha256:6238612dc401fa5c221c0ad7738f7ea43e48fe2695f6423e785ee2bc940f021d"}, +] + +[package.dependencies] +requests = ">=2.14.0" + +[[package]] +name = "pillow" +version = "12.0.0" +description = "Python Imaging Library (fork)" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}, + {file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"}, + {file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"}, + {file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"}, + {file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"}, + {file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"}, + {file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"}, + {file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"}, + {file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"}, + {file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"}, + {file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"}, + {file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"}, + {file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"}, + {file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"}, + {file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"}, + {file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"}, + {file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"}, + {file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"}, + {file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"}, + {file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"}, + {file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"}, + {file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"}, + {file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"}, + {file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +xmp = ["defusedxml"] + +[[package]] +name = "platformdirs" +version = "4.5.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, + {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, +] + +[package.extras] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] + +[[package]] +name = "plotly" +version = "6.4.0" +description = "An open-source interactive data visualization library for Python" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "plotly-6.4.0-py3-none-any.whl", hash = "sha256:a1062eafbdc657976c2eedd276c90e184ccd6c21282a5e9ee8f20efca9c9a4c5"}, + {file = "plotly-6.4.0.tar.gz", hash = "sha256:68c6db2ed2180289ef978f087841148b7efda687552276da15a6e9b92107052a"}, +] + +[package.dependencies] +narwhals = ">=1.15.1" +packaging = "*" + +[package.extras] +dev = ["plotly[dev-optional]"] +dev-build = ["build", "jupyter", "plotly[dev-core]"] +dev-core = ["pytest", "requests", "ruff (==0.11.12)"] +dev-optional = ["anywidget", "colorcet", "fiona (<=1.9.6) ; python_version <= \"3.8\"", "geopandas", "inflect", "numpy", "orjson", "pandas", "pdfrw", "pillow", "plotly-geo", "plotly[dev-build]", "plotly[kaleido]", "polars[timezone]", "pyarrow", "pyshp", "pytz", "scikit-image", "scipy", "shapely", "statsmodels", "vaex ; python_version <= \"3.9\"", "xarray"] +express = ["numpy"] +kaleido = ["kaleido (>=1.1.0)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pooch" +version = "1.8.2" +description = "A friend to fetch your data files" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47"}, + {file = "pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10"}, +] + +[package.dependencies] +packaging = ">=20.0" +platformdirs = ">=2.5.0" +requests = ">=2.19.0" + +[package.extras] +progress = ["tqdm (>=4.41.0,<5.0.0)"] +sftp = ["paramiko (>=2.7.0)"] +xxhash = ["xxhash (>=1.4.3)"] + +[[package]] +name = "psutil" +version = "7.1.3" +description = "Cross-platform lib for process and system monitoring." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc"}, + {file = "psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0"}, + {file = "psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7"}, + {file = "psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251"}, + {file = "psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa"}, + {file = "psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee"}, + {file = "psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353"}, + {file = "psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b"}, + {file = "psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9"}, + {file = "psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f"}, + {file = "psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7"}, + {file = "psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264"}, + {file = "psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab"}, + {file = "psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880"}, + {file = "psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3"}, + {file = "psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b"}, + {file = "psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd"}, + {file = "psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1"}, + {file = "psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74"}, +] + +[package.extras] +dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] +test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "setuptools", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] + +[[package]] +name = "pycparser" +version = "2.23" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "(platform_python_implementation != \"PyPy\" or os_name == \"nt\" and implementation_name != \"pypy\") and implementation_name != \"PyPy\"" +files = [ + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, +] + +[[package]] +name = "pyerfa" +version = "2.0.1.5" +description = "Python bindings for ERFA" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyerfa-2.0.1.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b282d7c60c4c47cf629c484c17ac504fcb04abd7b3f4dfcf53ee042afc3a5944"}, + {file = "pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:be1aeb70390dd03a34faf96749d5cabc58437410b4aab7213c512323932427df"}, + {file = "pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0603e8e1b839327d586c8a627cdc634b795e18b007d84f0cda5500a0908254e"}, + {file = "pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e43c7194e3242083f2350b46c09fd4bf8ba1bcc0ebd1460b98fc47fe2389906"}, + {file = "pyerfa-2.0.1.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:07b80cd70701f5d066b1ac8cce406682cfcd667a1186ec7d7ade597239a6021d"}, + {file = "pyerfa-2.0.1.5-cp39-abi3-win32.whl", hash = "sha256:d30b9b0df588ed5467e529d851ea324a67239096dd44703125072fd11b351ea2"}, + {file = "pyerfa-2.0.1.5-cp39-abi3-win_amd64.whl", hash = "sha256:66292d437dcf75925b694977aa06eb697126e7b86553e620371ed3e48b5e0ad0"}, + {file = "pyerfa-2.0.1.5-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4991dee680ff36c87911d8faa4c7d1aa6278ad9b5e0d16158cf22fa7d74ba25c"}, + {file = "pyerfa-2.0.1.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690e258294202c86f479e78e80fd235cd27bd717f7f60062fccc3dbd6ef0b1a9"}, + {file = "pyerfa-2.0.1.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:171ce9676a448a7eb555f03aa19ad5c749dbced1ce4f9923e4d93443c4a9c612"}, + {file = "pyerfa-2.0.1.5.tar.gz", hash = "sha256:17d6b24fe4846c65d5e7d8c362dcb08199dc63b30a236aedd73875cc83e1f6c0"}, +] + +[package.dependencies] +numpy = ">=1.19.3" + +[package.extras] +docs = ["sphinx-astropy (>=1.3)"] +test = ["pytest", "pytest-doctestplus (>=0.7)"] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyopenssl" +version = "25.3.0" +description = "Python wrapper module around the OpenSSL library" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6"}, + {file = "pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329"}, +] + +[package.dependencies] +cryptography = ">=45.0.7,<47" +typing-extensions = {version = ">=4.9", markers = "python_version < \"3.13\" and python_version >= \"3.8\""} + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"] +test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] + +[[package]] +name = "pyparsing" +version = "3.2.5" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, + {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyperf" +version = "2.9.0" +description = "Python module to run and analyze benchmarks" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyperf-2.9.0-py3-none-any.whl", hash = "sha256:215673fb60f3fbbc6c7a90b609b0806d8466fded8dd502bf5731e2b92506076e"}, + {file = "pyperf-2.9.0.tar.gz", hash = "sha256:dbe0feef8ec1a465df191bba2576149762d15a8c9985c9fea93ab625d875c362"}, +] + +[package.dependencies] +psutil = ">=5.9.0" + +[package.extras] +dev = ["tox"] + +[[package]] +name = "pyrecest" +version = "0.8.0" +description = "Framework for recursive Bayesian estimation in Python." +optional = false +python-versions = ">=3.10,<3.13" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +beartype = "*" +filterpy = "*" +matplotlib = "*" +mpmath = "*" +numpy = "*" +numpy-quaternion = "*" +pyshtools = "*" +scipy = "^1.14.1" +shapely = "*" + +[package.extras] +healpy-support = [] +jax-support = [] +pytorch-support = [] + +[package.source] +type = "git" +url = "https://github.com/KIT-ISAS/pyRecEst.git" +reference = "002c7757e2a5a81e4e57da5c1b657c5f9bd5cf6e" +resolved_reference = "002c7757e2a5a81e4e57da5c1b657c5f9bd5cf6e" + +[[package]] +name = "pyshtools" +version = "4.13.1" +description = "SHTOOLS - Spherical Harmonic Tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyshtools-4.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8f6235c5616c17a5b214af2575571dee7eb15f58ec5b382081ace47960a10e14"}, + {file = "pyshtools-4.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4059d67721c7ba033ae591cad8f32fa26149892e833e292d512824bbedce445"}, + {file = "pyshtools-4.13.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2926cbef344092ced413575001f012eeb001ef88d5c7e0fd5d45e21449471416"}, + {file = "pyshtools-4.13.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d7b566cfe7499f19e4d7db2eb336e1ad9a20faf609788e7b696bb3ad03312aa5"}, + {file = "pyshtools-4.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:31a3549c6d0b8299b5f7d7369c46d541c02e045f2db65b7b01949e0fdd0de60d"}, + {file = "pyshtools-4.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ea6b21d7d1e22361c7902b500a3a5da5f7ca24d8c866b3aa319a83f8ab09a96"}, + {file = "pyshtools-4.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4a1c1917fc7e968f27e0551d784f64eab7e344549b0e9bc77700b3b5c8f617a9"}, + {file = "pyshtools-4.13.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c73d6cdeee11fc058ba840ab12037a3390fa6ae18bca507f9ade559489aa0c09"}, + {file = "pyshtools-4.13.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0d8dfec4aa43486b0ea7f23654bf63f26309c4448dc8a06df43cd9affe18db11"}, + {file = "pyshtools-4.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:afc93c79de48b9e8ce7eb71e518356c1c11221a0791b37c827ef5fbd48d93e55"}, + {file = "pyshtools-4.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8e3131d2fd148b1f1089ae8e044c8b28ef988033fc1315ff600b65964fa179e0"}, + {file = "pyshtools-4.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bd367d5a389a2e7d6985ad4d093c486c13c47b1d632dd28ff273a0f73490ab"}, + {file = "pyshtools-4.13.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:53bbeb0a01fe8b6b7e662b05b3d0f67685d61c2c8f36cad363455923aeaa5353"}, + {file = "pyshtools-4.13.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f9a4d22a3305540788928421fb5673c102f14fd78904012e53a44bf438c2484e"}, + {file = "pyshtools-4.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:71f5ea1cddcb5b210a12488dc4899e50219e6fb8727d03c3584d433ac62e9248"}, + {file = "pyshtools-4.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f010fc0d610063ab16d7c6048fac6b611dc5d3398c53b515ef6789ee27c9b5c5"}, + {file = "pyshtools-4.13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:487f8368308ebac20b1eb2c6bd03a927f27121a02bdeeafe371b998776f34f71"}, + {file = "pyshtools-4.13.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:0ad505acc3e6e1a9839938e959d3c188fadcf915aad9c7680d9211246a55e32b"}, + {file = "pyshtools-4.13.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:dc798e61614a7411a90bc63d1a44faf55cfc90c842284cd27167759ea72a4e31"}, + {file = "pyshtools-4.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:8994277075608fb8eeaa5d1e43b66dfc50c2f5b276b3325197cd7d2107fcf875"}, + {file = "pyshtools-4.13.1.tar.gz", hash = "sha256:cc4a323e9cbc905c04ae9e2e9fedeea6d76f3315a6863ede353a4dec87b8c018"}, +] + +[package.dependencies] +astropy = ">=4.0" +matplotlib = ">=3.3" +numpy = ">=1.23.5" +pooch = ">=1.1" +requests = "*" +scipy = ">=0.14.0" +tqdm = "*" +xarray = "*" + +[package.extras] +cartopy = ["cartopy (>=0.18.0)"] +ducc = ["ducc0 (>=0.15)"] +palettable = ["palettable (>=3.3)"] +pygmt = ["pygmt (>=0.7)"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +description = "pytest plugin to abort hanging tests" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, + {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, +] + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "retrying" +version = "1.4.2" +description = "Retrying" +optional = false +python-versions = ">=3.6" +groups = ["main", "dev"] +files = [ + {file = "retrying-1.4.2-py3-none-any.whl", hash = "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59"}, + {file = "retrying-1.4.2.tar.gz", hash = "sha256:d102e75d53d8d30b88562d45361d6c6c934da06fab31bd81c0420acb97a8ba39"}, +] + +[[package]] +name = "scipy" +version = "1.15.3" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, + {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, + {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f"}, + {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92"}, + {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82"}, + {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40"}, + {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e"}, + {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c"}, + {file = "scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1"}, + {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889"}, + {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982"}, + {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9"}, + {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594"}, + {file = "scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c"}, + {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45"}, + {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49"}, + {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e"}, + {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539"}, + {file = "scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730"}, + {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825"}, + {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7"}, + {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11"}, + {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126"}, + {file = "scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb"}, + {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723"}, + {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb"}, + {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4"}, + {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5"}, + {file = "scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca"}, + {file = "scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.5" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "selenium" +version = "4.2.0" +description = "" +optional = false +python-versions = "~=3.7" +groups = ["dev"] +files = [ + {file = "selenium-4.2.0-py3-none-any.whl", hash = "sha256:ba5b2633f43cf6fe9d308fa4a6996e00a101ab9cb1aad6fd91ae1f3dbe57f56f"}, +] + +[package.dependencies] +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +urllib3 = {version = ">=1.26,<2.0", extras = ["secure", "socks"]} + +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "shapely" +version = "2.1.2" +description = "Manipulation and analysis of geometric objects" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "shapely-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7ae48c236c0324b4e139bea88a306a04ca630f49be66741b340729d380d8f52f"}, + {file = "shapely-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eba6710407f1daa8e7602c347dfc94adc02205ec27ed956346190d66579eb9ea"}, + {file = "shapely-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef4a456cc8b7b3d50ccec29642aa4aeda959e9da2fe9540a92754770d5f0cf1f"}, + {file = "shapely-2.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e38a190442aacc67ff9f75ce60aec04893041f16f97d242209106d502486a142"}, + {file = "shapely-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:40d784101f5d06a1fd30b55fc11ea58a61be23f930d934d86f19a180909908a4"}, + {file = "shapely-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6f6cd5819c50d9bcf921882784586aab34a4bd53e7553e175dece6db513a6f0"}, + {file = "shapely-2.1.2-cp310-cp310-win32.whl", hash = "sha256:fe9627c39c59e553c90f5bc3128252cb85dc3b3be8189710666d2f8bc3a5503e"}, + {file = "shapely-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:1d0bfb4b8f661b3b4ec3565fa36c340bfb1cda82087199711f86a88647d26b2f"}, + {file = "shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618"}, + {file = "shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d"}, + {file = "shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09"}, + {file = "shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26"}, + {file = "shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7"}, + {file = "shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2"}, + {file = "shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6"}, + {file = "shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc"}, + {file = "shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94"}, + {file = "shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359"}, + {file = "shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3"}, + {file = "shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b"}, + {file = "shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc"}, + {file = "shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d"}, + {file = "shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454"}, + {file = "shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179"}, + {file = "shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8"}, + {file = "shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a"}, + {file = "shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e"}, + {file = "shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6"}, + {file = "shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af"}, + {file = "shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd"}, + {file = "shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350"}, + {file = "shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715"}, + {file = "shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40"}, + {file = "shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b"}, + {file = "shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801"}, + {file = "shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0"}, + {file = "shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c"}, + {file = "shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99"}, + {file = "shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf"}, + {file = "shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c"}, + {file = "shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223"}, + {file = "shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c"}, + {file = "shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df"}, + {file = "shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf"}, + {file = "shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4"}, + {file = "shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc"}, + {file = "shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566"}, + {file = "shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c"}, + {file = "shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a"}, + {file = "shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076"}, + {file = "shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1"}, + {file = "shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0"}, + {file = "shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26"}, + {file = "shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0"}, + {file = "shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735"}, + {file = "shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9"}, + {file = "shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9"}, +] + +[package.dependencies] +numpy = ">=1.21" + +[package.extras] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov", "scipy-doctest"] + +[[package]] +name = "simplejson" +version = "3.20.2" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.5" +groups = ["dev"] +files = [ + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:11847093fd36e3f5a4f595ff0506286c54885f8ad2d921dfb64a85bce67f72c4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d291911d23b1ab8eb3241204dd54e3ec60ddcd74dfcb576939d3df327205865"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:da6d16d7108d366bbbf1c1f3274662294859c03266e80dd899fc432598115ea4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9ddf9a07694c5bbb4856271cbc4247cc6cf48f224a7d128a280482a2f78bae3d"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3a0d2337e490e6ab42d65a082e69473717f5cc75c3c3fb530504d3681c4cb40c"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8ba88696351ed26a8648f8378a1431223f02438f8036f006d23b4f5b572778fa"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:00bcd408a4430af99d1f8b2b103bb2f5133bb688596a511fcfa7db865fbb845e"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4fc62feb76f590ccaff6f903f52a01c58ba6423171aa117b96508afda9c210f0"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7"}, + {file = "simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53"}, + {file = "simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413"}, + {file = "simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961"}, + {file = "simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544"}, + {file = "simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54"}, + {file = "simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769"}, + {file = "simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661"}, + {file = "simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608"}, + {file = "simplejson-3.20.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a135941a50795c934bdc9acc74e172b126e3694fe26de3c0c1bc0b33ea17e6ce"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ba488decb18738f5d6bd082018409689ed8e74bc6c4d33a0b81af6edf1c9f4"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81f8e982923d5e9841622ff6568be89756428f98a82c16e4158ac32b92a3787"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdad497ccb1edc5020bef209e9c3e062a923e8e6fca5b8a39f0fb34380c8a66c"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a3f1db97bcd9fb592928159af7a405b18df7e847cbcc5682a209c5b2ad5d6b1"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:215b65b0dc2c432ab79c430aa4f1e595f37b07a83c1e4c4928d7e22e6b49a748"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:ece4863171ba53f086a3bfd87f02ec3d6abc586f413babfc6cf4de4d84894620"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:4a76d7c47d959afe6c41c88005f3041f583a4b9a1783cf341887a3628a77baa0"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e9b0523582a57d9ea74f83ecefdffe18b2b0a907df1a9cef06955883341930d8"}, + {file = "simplejson-3.20.2-cp36-cp36m-win32.whl", hash = "sha256:16366591c8e08a4ac76b81d76a3fc97bf2bcc234c9c097b48d32ea6bfe2be2fe"}, + {file = "simplejson-3.20.2-cp36-cp36m-win_amd64.whl", hash = "sha256:732cf4c4ac1a258b4e9334e1e40a38303689f432497d3caeb491428b7547e782"}, + {file = "simplejson-3.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6c3a98e21e5f098e4f982ef302ebb1e681ff16a5d530cfce36296bea58fe2396"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cf9ca1363dc3711c72f4ec7c1caed2bbd9aaa29a8d9122e31106022dc175c6"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:106762f8aedf3fc3364649bfe8dc9a40bf5104f872a4d2d86bae001b1af30d30"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b21659898b7496322e99674739193f81052e588afa8b31b6a1c7733d8829b925"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fa1db6a02bca88829f2b2057c76a1d2dc2fccb8c5ff1199e352f213e9ec719"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:156139d94b660448ec8a4ea89f77ec476597f752c2ff66432d3656704c66b40e"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:b2620ac40be04dff08854baf6f4df10272f67079f61ed1b6274c0e840f2e2ae1"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:9ccef5b5d3e3ac5d9da0a0ca1d2de8cf2b0fb56b06aa0ab79325fa4bcc5a1d60"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f526304c2cc9fd8b8d18afacb75bc171650f83a7097b2c92ad6a431b5d7c1b72"}, + {file = "simplejson-3.20.2-cp37-cp37m-win32.whl", hash = "sha256:e0f661105398121dd48d9987a2a8f7825b8297b3b2a7fe5b0d247370396119d5"}, + {file = "simplejson-3.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dab98625b3d6821e77ea59c4d0e71059f8063825a0885b50ed410e5c8bd5cb66"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b8205f113082e7d8f667d6cd37d019a7ee5ef30b48463f9de48e1853726c6127"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fc8da64929ef0ff16448b602394a76fd9968a39afff0692e5ab53669df1f047f"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfe704864b5fead4f21c8d448a89ee101c9b0fc92a5f40b674111da9272b3a90"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ca7cbe7d2f423b97ed4e70989ef357f027a7e487606628c11b79667639dc84"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cec1868b237fe9fb2d466d6ce0c7b772e005aadeeda582d867f6f1ec9710cad"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:792debfba68d8dd61085ffb332d72b9f5b38269cda0c99f92c7a054382f55246"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e022b2c4c54cb4855e555f64aa3377e3e5ca912c372fa9e3edcc90ebbad93dce"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5de26f11d5aca575d3825dddc65f69fdcba18f6ca2b4db5cef16f41f969cef15"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e2162b2a43614727ec3df75baeda8881ab129824aa1b49410d4b6c64f55a45b4"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e11a1d6b2f7e72ca546bdb4e6374b237ebae9220e764051b867111df83acbd13"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:daf7cd18fe99eb427fa6ddb6b437cfde65125a96dc27b93a8969b6fe90a1dbea"}, + {file = "simplejson-3.20.2-cp38-cp38-win32.whl", hash = "sha256:da795ea5f440052f4f497b496010e2c4e05940d449ea7b5c417794ec1be55d01"}, + {file = "simplejson-3.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:6a4b5e7864f952fcce4244a70166797d7b8fd6069b4286d3e8403c14b88656b6"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bf76512ccb07d47944ebdca44c65b781612d38b9098566b4bb40f713fc4047"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:214e26acf2dfb9ff3314e65c4e168a6b125bced0e2d99a65ea7b0f169db1e562"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fb1259ca9c385b0395bad59cdbf79535a5a84fb1988f339a49bfbc57455a35a"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34e028a2ba8553a208ded1da5fa8501833875078c4c00a50dffc33622057881"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b538f9d9e503b0dd43af60496780cb50755e4d8e5b34e5647b887675c1ae9fee"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab998e416ded6c58f549a22b6a8847e75a9e1ef98eb9fbb2863e1f9e61a4105b"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a8f1c307edf5fbf0c6db3396c5d3471409c4a40c7a2a466fbc762f20d46601a"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a7bbac80bdb82a44303f5630baee140aee208e5a4618e8b9fde3fc400a42671"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5ef70ec8fe1569872e5a3e4720c1e1dcb823879a3c78bc02589eb88fab920b1f"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11c09c99253a74c36925d461c86ea25f0140f3b98ff678322734ddc0f038d7"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66f7c78c6ef776f8bd9afaad455e88b8197a51e95617bcc44b50dd974a7825ba"}, + {file = "simplejson-3.20.2-cp39-cp39-win32.whl", hash = "sha256:619ada86bfe3a5aa02b8222ca6bfc5aa3e1075c1fb5b3263d24ba579382df472"}, + {file = "simplejson-3.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:44a6235e09ca5cc41aa5870a952489c06aa4aee3361ae46daa947d8398e57502"}, + {file = "simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017"}, + {file = "simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "soupsieve" +version = "2.8" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, + {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, +] + +[[package]] +name = "sphstat" +version = "1.0.6" +description = "A Python 3 package for inferential statistics on vectorial data on the unit sphere" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +matplotlib = "*" +numpy = "*" +openpyxl = "*" +pandas = "*" +scipy = "*" +setuptools = ">=65.5.1" +sympy = "*" + +[package.source] +type = "git" +url = "https://github.com/Vlad-Kor/sphstat" +reference = "HEAD" +resolved_reference = "fad7f8779d4b4f8eeccae0f5e1e2ed6dd7280a22" + +[[package]] +name = "sympy" +version = "1.14.0" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, + {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "tomli" +version = "2.3.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "trio" +version = "0.32.0" +description = "A friendly Python library for async concurrency and I/O" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "trio-0.32.0-py3-none-any.whl", hash = "sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5"}, + {file = "trio-0.32.0.tar.gz", hash = "sha256:150f29ec923bcd51231e1d4c71c7006e65247d68759dd1c19af4ea815a25806b"}, +] + +[package.dependencies] +attrs = ">=23.2.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = "*" +outcome = "*" +sniffio = ">=1.3.0" +sortedcontainers = "*" + +[[package]] +name = "trio-websocket" +version = "0.12.2" +description = "WebSocket library for Trio" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6"}, + {file = "trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +outcome = ">=1.2.0" +trio = ">=0.11" +wsproto = ">=0.14" + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "tzdata" +version = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + +[[package]] +name = "urllib3" +version = "1.26.20" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "dev"] +files = [ + {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, + {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, +] + +[package.dependencies] +certifi = {version = "*", optional = true, markers = "extra == \"secure\""} +cryptography = {version = ">=1.3.4", optional = true, markers = "extra == \"secure\""} +idna = {version = ">=2.0.0", optional = true, markers = "extra == \"secure\""} +pyOpenSSL = {version = ">=0.14", optional = true, markers = "extra == \"secure\""} +PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} +urllib3-secure-extra = {version = "*", optional = true, markers = "extra == \"secure\""} + +[package.extras] +brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "urllib3-secure-extra" +version = "0.1.0" +description = "Marker library to detect whether urllib3 was installed with the deprecated [secure] extra" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "urllib3-secure-extra-0.1.0.tar.gz", hash = "sha256:ee9409cbfeb4b8609047be4c32fb4317870c602767e53fd8a41005ebe6a41dff"}, + {file = "urllib3_secure_extra-0.1.0-py2.py3-none-any.whl", hash = "sha256:f7adcb108b4d12a4b26b99eb60e265d087f435052a76aefa396b6ee85e9a6ef9"}, +] + +[[package]] +name = "vtk" +version = "9.5.2" +description = "VTK is an open-source toolkit for 3D computer graphics, image processing, and visualization" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "vtk-9.5.2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9ca87122352cf3c8748fee73c48930efa46fe1a868149a1f760bc17e8fae27ba"}, + {file = "vtk-9.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6da02d69dcf2d42472ec8c227e6a8406cedea53d3928af97f8d4e776ff89c95f"}, + {file = "vtk-9.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0ba9cc4b5cd463a1984dfac6d0a9eeef888b273208739f8ebc46d392ddabb93"}, + {file = "vtk-9.5.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c9254f864ebef3d69666a1feedf09cad129e4c91f85ca804c38cf8addedb2748"}, + {file = "vtk-9.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:7c56dbd02e5b4ec0422886bf9e26059ad2d4622857dbfb90d9ed254104fd9d6c"}, + {file = "vtk-9.5.2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:afcbc6dc122ebba877793940fda8fd2cbe14e1dae590e6872ea74894abdab9be"}, + {file = "vtk-9.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:005877a568b96cf00ceb5bec268cf102db756bed509cb240fa40ada414a24bf0"}, + {file = "vtk-9.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2e2fe2535483adb1ba8cc83a0dc296faaffa2505808a3b04f697084f656e5f84"}, + {file = "vtk-9.5.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0248aab2ee51a69fadcdcf74697a045e2d525009a35296100eed2211f0cca2bb"}, + {file = "vtk-9.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:f78674fd265022499ea6b7f03d7f11a861e89e1df043592a82e4f5235c537ef5"}, + {file = "vtk-9.5.2-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:cf5dbc93b6806b08799204430a4fc4bea74290c1c101fa64f1a4703144087fa3"}, + {file = "vtk-9.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cce212b911d13fb0ca36d339f658c9db1ff27a5a730cdddd5d0c6b2ec24c15b1"}, + {file = "vtk-9.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:454711c51038824ddc75f955e1064c4e214b452c2e67083f01a8b43fc0ed62cb"}, + {file = "vtk-9.5.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9fce9688f0dede00dc6f3b046037c5fa8378479fa8303a353fd69afae4078d9a"}, + {file = "vtk-9.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:5357bccdf8629373195cab871e45c50383d052d316192aa48f45bd9f87bafccb"}, + {file = "vtk-9.5.2-cp313-cp313-macosx_10_10_x86_64.whl", hash = "sha256:1eae5016620a5fd78f4918256ea65dbe100a7c3ce68f763b64523f06aaaeafbc"}, + {file = "vtk-9.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:29ad766e308dcaa23b36261180cd9960215f48815b31c7ac2aa52edc88e21ef7"}, + {file = "vtk-9.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11cf870c05539e9f82f4a5adf450384e0be4ee6cc80274f9502715a4139e2777"}, + {file = "vtk-9.5.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3c4b658d61815cb87177f4e94281396c9be5a28798464a2c6fa0897b1bba282f"}, + {file = "vtk-9.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:974783b8865e2ddc2818d3090705b6bc6bf8ae40346d67f9a43485fabcfb3a99"}, + {file = "vtk-9.5.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:227c5e6e9195aa9d92a64d6d07d09f000576b5df231522b5c156a3c4c4190d69"}, + {file = "vtk-9.5.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5d0a89e893d9279ba9742d0bbd47d7dfac96fccd8fb9d024bb8aa098fde5637"}, + {file = "vtk-9.5.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:371d9068f5cb25861aa51c1d1792fffce5a44032dbece55412562429c5f257cc"}, + {file = "vtk-9.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:7cf2e2e12184c018388f06fbffcb93ea9e478ca4bf636c3f66bd7503e2230298"}, + {file = "vtk-9.5.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b148e57837d1fd2a8a72f171a0fb40872837dea191f673f2b7ec397935c754e"}, + {file = "vtk-9.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6a3b27f22d7e15f6a2d60510e70d75dac4ed2a53600e31275b67fedc45afbcc0"}, + {file = "vtk-9.5.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b6b91968581132b0d96142a08d50028efa5aa7a876d4aff6de1664e99e006c89"}, + {file = "vtk-9.5.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2b37670d56de32935eeadee58e1a9a0b5d3847294ca24ea9329101089be5de83"}, + {file = "vtk-9.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:1ac9ff528892e585f8f3286b26a90250bd6ea9107c38e6e194939f6f28269ad6"}, +] + +[package.dependencies] +matplotlib = ">=2.0.0" + +[package.extras] +numpy = ["numpy (>=1.9)"] +web = ["wslink (>=1.0.4)"] + +[[package]] +name = "waitress" +version = "3.0.2" +description = "Waitress WSGI server" +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"}, + {file = "waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"}, +] + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["coverage (>=7.6.0)", "pytest", "pytest-cov"] + +[[package]] +name = "werkzeug" +version = "3.0.6" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, + {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +groups = ["dev"] +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "xarray" +version = "2025.6.1" +description = "N-D labeled arrays and datasets in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "xarray-2025.6.1-py3-none-any.whl", hash = "sha256:8b988b47f67a383bdc3b04c5db475cd165e580134c1f1943d52aee4a9c97651b"}, + {file = "xarray-2025.6.1.tar.gz", hash = "sha256:a84f3f07544634a130d7dc615ae44175419f4c77957a7255161ed99c69c7c8b0"}, +] + +[package.dependencies] +numpy = ">=1.24" +packaging = ">=23.2" +pandas = ">=2.1" + +[package.extras] +accel = ["bottleneck", "flox", "numba (>=0.54)", "numbagg", "opt_einsum", "scipy"] +complete = ["xarray[accel,etc,io,parallel,viz]"] +etc = ["sparse"] +io = ["cftime", "fsspec", "h5netcdf", "netCDF4", "pooch", "pydap ; python_version < \"3.10\"", "scipy", "zarr"] +parallel = ["dask[complete]"] +types = ["pandas-stubs", "scipy-stubs", "types-PyYAML", "types-Pygments", "types-colorama", "types-decorator", "types-defusedxml", "types-docutils", "types-networkx", "types-openpyxl", "types-pexpect", "types-psutil", "types-pycurl", "types-python-dateutil", "types-pytz", "types-setuptools"] +viz = ["cartopy", "matplotlib", "nc-time-axis", "seaborn"] + +[[package]] +name = "zipp" +version = "3.23.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.10,<3.11" +content-hash = "527df857cfe567834036aece62eb46ae0d47b15233b2ff08faf1ee45523ab7d8" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5366006 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[project] +name = "web-app" +version = "0.1.0" +description = "Interactive Visualization Webapp with Plotly/Dash" +authors = [ + {name = "Vlad Korsakov",email = "ulqba@student.kit.edu"}, + {name = "Daniel Frisch",email = "daniel.frisch@kit.edu"} +] +readme = "README.md" +requires-python = ">=3.10,<3.11" +dependencies = [ + "dash (>=2.16,<3.0)", + "dash-bootstrap-components (>=1.6,<2.0)", + "numpy (>=2.0,<3.0)", + "dash-mantine-components (>=0.14,<0.15)", + "pandas (>=2.2,<3.0)", + "dash-vtk (>=0.0.9,<0.0.10)", + "dash-resizable-panels (>=0.1.0,<0.2.0)", + "scipy (>=1.13,<2.0)", + "gunicorn (>=23.0.0,<24.0.0)", + "sphstat @ git+https://github.com/Vlad-Kor/sphstat", + "pyrecest @ git+https://github.com/KIT-ISAS/pyRecEst.git@002c7757e2a5a81e4e57da5c1b657c5f9bd5cf6e", + "kent-distribution @ git+https://github.com/Vlad-Kor/kent_distribution", + "sympy (>=1.14.0,<2.0.0)", + "deterministic-gaussian-sampling-fibonacci @ git+https://github.com/KIT-ISAS/deterministic_gaussian_sampling_fibonacci", +] + +[dependency-groups] +dev = [ + "pytest (>=8.4.2,<9.0.0)", + "dash[testing] (>=2.16,<3.0)", + "pyperf (>=2.9.0,<3.0.0)", + "kaleido (>=1.2.0,<2.0.0)" +] + + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +package-mode = false + +[tool.poetry.requires-plugins] +poetry-plugin-export = ">=1.8" + diff --git a/readme.txt b/readme.txt deleted file mode 100644 index 06fdc52..0000000 --- a/readme.txt +++ /dev/null @@ -1,8 +0,0 @@ - -source .venv/bin/activate -pip install dash -pip install pandas -pip install dash-bootstrap-components -pip install dash-mantine-components -pip install numpy - diff --git a/renderer/__init__.py b/renderer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/renderer/object_3D_and_2D_renderer.py b/renderer/object_3D_and_2D_renderer.py new file mode 100644 index 0000000..c9f917b --- /dev/null +++ b/renderer/object_3D_and_2D_renderer.py @@ -0,0 +1,351 @@ +from dash import html, dcc, callback, Input, Output, ALL, State, Patch, no_update +import numpy as np +import plotly.graph_objects as go + +from renderer.object_3D_renderer import Object3DRenderer + +class Object3DAnd2DRenderer(Object3DRenderer): + def __init__(self, object, id): + # the renderer starts out in 3d mode + super().__init__(object, id, register_3d_callbacks=False) + self.register_plot_callbacks() + self.register_mode_callbacks() + + self.per_x = object.plot_settings_2d.periodic_x + self.per_y = object.plot_settings_2d.periodic_y + + self.perx_x_amount = object.plot_settings_2d.periodic_x_amount + self.pery_y_amount = object.plot_settings_2d.periodic_y_amount + + self.reverse_x_y_axis = object.plot_settings_2d.reverse_x_y_axis + + x_min, y_min, x_max, y_max = object.plot_settings_2d.color_location + self.color_heatmap_x = np.linspace(x_min, x_max, 100) + self.color_heatmap_y = np.linspace(y_min, y_max, 100) + self.color_meshgrid = np.meshgrid(self.color_heatmap_x, self.color_heatmap_y, indexing="xy") + + padd = 0.5 + + self.fig_2d = go.Figure( + data=[ + go.Scattergl( + name="Samples", + x=[], + y=[], + mode="markers", + marker=dict( + size=6, + color="red", + line=dict(width=1, color="black") + ), + showlegend=True, + ), + + go.Scattergl( + name="Samples (periodic extension)", + x=[], + y=[], + mode="markers", + marker=dict( + size=6, + color="#a2acbd", + line=dict(width=1, color="#a2acbd") + ), + showlegend=(object.plot_settings_2d.periodic_x or object.plot_settings_2d.periodic_y) + ), + + go.Heatmap( + name="PDF", + x=self.color_heatmap_x, + y=self.color_heatmap_y, + z=np.zeros((100,100)), + colorscale="cividis", + zmin=0.0, + zmax=1.0, + zsmooth="best", + colorbar=dict(title="pdf"), + showscale=True, + showlegend=True, + ) + ] + ) + self.fig_2d.update_layout(legend=dict( + yanchor="top", + y=0.99, + xanchor="left", + x=0.01, + )) + self.fig_2d.update_layout(dragmode="pan") + # keep user zoom/pan while data is being patched + self.fig_2d.update_layout(uirevision=f"object-2d-{self.id}") + + if object.plot_settings_2d is not None: + self.fig_2d.update_xaxes( + title_text=object.plot_settings_2d.x_title, + tickmode="array", + tickvals=object.plot_settings_2d.axes_2d_x[0], + ticktext=object.plot_settings_2d.axes_2d_x[1], + zeroline=False, + ) + self.fig_2d.update_yaxes( + title_text=object.plot_settings_2d.y_title, + tickmode="array", + tickvals=object.plot_settings_2d.axes_2d_y[0], + ticktext=object.plot_settings_2d.axes_2d_y[1], + zeroline=False, + ) + + if object.plot_settings_2d.lock_aspect_ratio: + self.fig_2d.update_yaxes( + scaleanchor = "x", + scaleratio = 1, + ) + + # so that it doent autoscale for periodicity points + padd = 0.5 + if self.per_x: + self.fig_2d.update_xaxes( + range=[0 - padd, self.perx_x_amount + padd], + ) + if self.per_y: + self.fig_2d.update_yaxes( + range=[0 - padd, self.pery_y_amount + padd], + ) + + + + def register_plot_callbacks(self): + # updates the plot based on selected sampling options + @callback( + Output(f"graph-{self.id}", "figure", allow_duplicate=True), + State(f"mode-selector-{self.id}", "value"), + Input(f"mode-done-{self.id}", "data"), + Input({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "value"), + State({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "id"), + Input({"type": "sampling", "renderer": self.id, "index": ALL, "manual": ALL}, "value"), + State({"type": "sampling", "renderer": self.id, "index": ALL, "manual": ALL}, "id"), + Input("distribution-selector", "value"), + Input(f"sampling-selector-{self.id}", "value"), + Input(f"distribution-options-{self.id}", "children"), + State(f"device-pixel-ratio-{self.id}", "data"), + prevent_initial_call='initial_duplicate' + ) + def update_plot_sample_callback(mode, _mode_counter, values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr): + if mode == "3D View": + return self.update_plot_sample(values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr) + else: + return self.update_plot_sample_2d(values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr) + + # updates the plot based on selected distribution options + @callback( + Output(f"graph-{self.id}", "figure", allow_duplicate=True), + State(f"mode-selector-{self.id}", "value"), + Input(f"mode-done-{self.id}", "data"), + Input({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "value"), + State({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "id"), + Input("distribution-selector", "value"), + Input(f"sampling-selector-{self.id}", "value"), + Input(f"distribution-options-{self.id}", "children"), + prevent_initial_call='initial_duplicate' + ) + def update_plot_dist_callback(mode, _mode_counter, values_dist, ids_dist, selected_distribution, selected_sampling, _): + if mode == "3D View": + return self.update_plot_dist(values_dist, ids_dist, selected_distribution, selected_sampling, _) + else: + return self.update_plot_dist_2d(values_dist, ids_dist, selected_distribution, selected_sampling, _) + + + def register_mode_callbacks(self): + @callback( + Output(f"graph-{self.id}", "figure"), + Output(f"mode-done-{self.id}", "data"), + Input(f"mode-selector-{self.id}", "value"), + State(f"mode-done-{self.id}", "data"), + ) + def switch_mode(mode, data): + new_data = (data + 1 if data is not None else 1) + if mode == "3D View": + return self.fig, new_data + else: + return self.fig_2d, new_data + + def update_plot_sample_2d(self, values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr): + try: + dist_options = self.object.distributions[selected_distribution].distribution_options + sampling_options = self.object.distributions[selected_distribution].sampling_method_dict[selected_sampling].sample_options + except KeyError: + # got stale values, ignore + return no_update + + # the order of options might not be guaranteed, so we map them by their ids + id_value_dist = [(id,v) for id, v in zip(ids_dist, values_dist)] + id_value_samp = [(id,v) for id, v in zip(ids_samp, values_samp)] + + + # and them sort them, so they are in the same order as sampling_options and dist_options + options_samp_new = sorted(id_value_samp, key=lambda x: int(x[0]["index"])) + options_dist_new = sorted(id_value_dist, key=lambda x: int(x[0]["index"])) + + + for opt, (id, new_state) in zip(sampling_options, options_samp_new): + opt.update_state(new_state) + + for opt, (id, new_state) in zip(dist_options, options_dist_new): + opt.update_state(new_state) + + # samples + samples, samples_2d = self.object.update_sample(selected_distribution, selected_sampling, sampling_options, dist_options) + + patched_figure = Patch() + tp = samples_2d + + # marker size scaling + sample_count = samples.shape[0] + if sample_count == 0: + marker_size = 0 # no samples, no size + else: + marker_size = (10 * (sample_count / 100) ** (-0.35)) / dpr + marker_size = np.minimum(10,marker_size) + + patched_figure["data"][0].marker.size = marker_size * 1.5 + patched_figure["data"][1].marker.size = marker_size + + if not self.reverse_x_y_axis: + # True: (eg torus: x is p, y is t) + # False: (eg cylinder: x is p, y is z) + tp[:, [0, 1]] = tp[:, [1, 0]] # swap order, below code assumes self.reverse_x_y_axis is True + + patched_figure["data"][0].x = tp[:, 1] + patched_figure["data"][0].y = tp[:, 0] + + + + + ext_x = np.array([]) + ext_y = np.array([]) + if self.per_x: + ext_x = np.concatenate([tp[:,1] - self.perx_x_amount, tp[:,1] + self.perx_x_amount]) + ext_y = np.concatenate([tp[:,0], tp[:,0]]) + + if self.per_y: + ext_x = np.concatenate([ext_x, tp[:,1], tp[:,1]]) + ext_y = np.concatenate([ext_y, tp[:,0] - self.pery_y_amount, tp[:,0] + self.pery_y_amount]) + + if self.per_x and self.per_y: # all four corners + ext_x = np.concatenate([ + ext_x, + tp[:,1] + self.perx_x_amount, + tp[:,1] + self.perx_x_amount, + tp[:,1] - self.perx_x_amount, + tp[:,1] - self.perx_x_amount + ]) + + ext_y = np.concatenate([ + ext_y, + tp[:,0] + self.pery_y_amount, + tp[:,0] - self.pery_y_amount, + tp[:,0] + self.pery_y_amount, + tp[:,0] - self.pery_y_amount + ]) + + + if self.per_x or self.per_y: + patched_figure["data"][1].x = ext_x + patched_figure["data"][1].y = ext_y + + return patched_figure + + + def update_plot_dist_2d(self, values_dist, ids_dist, selected_distribution, selected_sampling, _): + patched_figure = Patch() + try: + dist_options = self.object.distributions[selected_distribution].distribution_options + except KeyError: + # got stale values, ignore + return no_update + + # the order of options might not be guaranteed, so we map them by their ids + id_value_dist = [(id,v) for id, v in zip(ids_dist, values_dist)] + + + # and them sort them, so they are in the same order as sampling_options and dist_options + options_dist_new = sorted(id_value_dist, key=lambda x: int(x[0]["index"])) + + + for opt, (id, new_state) in zip(dist_options, options_dist_new): + opt.update_state(new_state) + + pdf = self.object.distributions[selected_distribution].get_pdf(list(dist_options)) + + # pdf heatmap + X, Y = self.color_meshgrid + if self.reverse_x_y_axis: + xy = np.column_stack((Y.ravel(), X.ravel())) + else: + xy = np.column_stack((X.ravel(), Y.ravel())) + z_flat = self.object.pdf_2d(xy, pdf).reshape(X.shape) + + z = z_flat.reshape(X.shape) + + patched_figure["data"][2].z = z + + return patched_figure + + def get_layout_components(self): + initial_distribution = self.object.distributions[list(self.object.distributions.keys())[0]] + initial_sampling_method = initial_distribution.sampling_methods[0].get_name() if initial_distribution.sampling_methods else "no sampling methods found" + initial_sampling_options = [x.get_name() for x in initial_distribution.sampling_methods] + + options = [ + dcc.Store(id=f"device-pixel-ratio-{self.id}", data=1), + + html.P("Select Visualization Mode:"), + + # needed to create dependecy between mode selector and plot update callbacks + # to insure they are fired after the plot mode finished changing + # data is just a counter that is incremented each time the mode is changed, + # so dash detects a change and fires callbacks depending on it (update samp and dist) + dcc.Store(id=f"mode-done-{self.id}", data=0), + + dcc.RadioItems( + id=f"mode-selector-{self.id}", + options=["3D View", "2D View"], + value="3D View", + inline=True, + labelStyle={"marginRight": "15px"} + ), + html.Hr(), + html.Br(), + html.P("Select Distribution and Sampling Method:"), + dcc.RadioItems( + id="distribution-selector", + options=(list(self.object.distributions.keys())), + value=initial_distribution.get_name() if self.object.distributions else "no distributions found", + ), + html.Br(), + dcc.RadioItems( + id=f"sampling-selector-{self.id}", + options=(list(initial_sampling_options)), + value=initial_sampling_method, + ), + html.Br(), + html.Hr(), + html.Br(), + + html.Div(id=f"distribution-options-{self.id}"), + html.Div(id=f"sampling-options-{self.id}"), + + html.Hr(id=f"distribution-info-divider-{self.id}", hidden=True), + + dcc.Markdown(id=f"distribution-info-markdown-{self.id}", mathjax=True), + + html.Hr(id=f"sampling-method-info-divider-{self.id}", hidden=True), + + dcc.Markdown(id=f"sampling-method-info-markdown-{self.id}", mathjax=True), + + dcc.Markdown(id=f"sampling-method-info-markdown-{self.id}", mathjax=True), + ] + + graph = [dcc.Graph(id=f"graph-{self.id}", figure=self.fig, config=self.config, style={'height': '100%'})] + + return options, graph diff --git a/renderer/object_3D_renderer.py b/renderer/object_3D_renderer.py new file mode 100644 index 0000000..c5b7bb2 --- /dev/null +++ b/renderer/object_3D_renderer.py @@ -0,0 +1,377 @@ +from functools import lru_cache +from dash import html, dcc, callback, Input, Output, ALL, State, Patch, clientside_callback, ClientsideFunction, MATCH, no_update +import numpy as np +import plotly.graph_objects as go +import plotly.figure_factory as ff +import dash + +from renderer.renderer import Renderer +class Object3DRenderer(Renderer): + def __init__(self, object_3D, id, register_3d_callbacks=True): + # dash doesnt like duplicate calback functions + # so each renderer instance gets a uuid for suffixing + self.id = id + self.device_pixel_ratio = 1.0 + + # objects should have atleast one corresponding distribution + self.object = object_3D + + self.x, self.y, self.z = self.object.xyz + + + # inital figure + self.config = { + 'responsive': True, + 'scrollZoom': True, + "modeBarButtonsToRemove": ["select2d", "lasso2d"], + "toImageButtonOptions": { + "format": "jpeg", + "scale": 8, + } + } + + marker_size = 4 # initial size, will be updated based on samples + + self.fig = go.Figure( + data=[ + go.Surface( + name="Surface", + x=self.x, y=self.y, z=self.z, + colorscale="Viridis", + showscale=False, + showlegend=True, + ), + go.Scatter3d( + name="Samples", + x=[], + y=[], + z=[], + mode="markers", + marker=dict( + size=marker_size, + color="red", + line=dict(width=1, color="black") + ), + ), + go.Scatter3d( + name="Density", + x=[], y=[], z=[], + mode="lines", + line=dict(color="#212121", width=0.5), + showlegend=True, + + ), + ] + ) + + self.fig.update_layout( + scene=dict( + aspectmode='data', + xaxis=dict(visible=False), + yaxis=dict(visible=False), + zaxis=dict(visible=False), + ), + margin=dict(l=0, r=0, t=0, b=0), + # keep camera/zoom/pan while data is patched + uirevision=f"object-3d-{self.id}", + ) + + # Keep legend overlayed in the corner so it does not steal width from narrow layouts + self.fig.update_layout(legend=dict( + yanchor="top", + y=0.98, + xanchor="left", + x=0.02, + bgcolor="rgba(255,255,255,0.7)" + )) + + if (self.object.camera_settings_3d is not None): + self.fig.update_layout(scene=dict(camera=self.object.camera_settings_3d)) + + self._register_callbacks() + if register_3d_callbacks: + self._register_3d_plot_callbacks() + + + def _register_callbacks(self): + + # gets device pixel ratio from scaling of samples + clientside_callback( + ClientsideFunction(namespace="utils", function_name="getDevicePixelRatio"), + Output(f"device-pixel-ratio-{self.id}", "data"), + Input(f"graph-{self.id}", "figure"), + ) + + # updates wich sampling methods are available once distribution is selected + @callback( + Output(f"sampling-selector-{self.id}", "options"), + Output(f"sampling-selector-{self.id}", "value"), + Input("distribution-selector", "value"), + ) + def update_sampling_methods(selected_distribution): + options = list(self.object.distributions[selected_distribution].sampling_method_dict.keys()) + + # set safe initial value + initial_value = options[0] + return options, initial_value + + # updates the options (silders, etc) for the selected distribution and sampling method + # also updates the info markdowns and their hr lines + @callback( + Output(f"distribution-options-{self.id}", "children"), + Output(f"sampling-options-{self.id}", "children"), + Output(f"distribution-info-markdown-{self.id}", "children"), + Output(f"sampling-method-info-markdown-{self.id}", "children"), + Output(f"distribution-info-divider-{self.id}", "hidden"), + Output(f"sampling-method-info-divider-{self.id}", "hidden"), + Input("distribution-selector", "value"), + Input(f"sampling-selector-{self.id}", "value") + ) + def update_curr_distribution(selected_distribution, selected_sampling): + # ids are given in the same order as options_dist and options_sampling + options_dist = self.object.distributions[selected_distribution].distribution_options + options_dist_dcc = [opt.to_dash_component("dist", id, self.id) for id, opt in enumerate(options_dist)] + + options_sampling = self.object.distributions[selected_distribution].sampling_method_dict[selected_sampling] + options_sampling_dcc = [opt.to_dash_component("sampling", id, self.id) for id, opt in enumerate(options_sampling.sample_options)] + + dist_info_md = self.object.distributions[selected_distribution].info_md + sampling_info_md = options_sampling.info_md + + dist_hidden = dist_info_md is None or dist_info_md.strip() == "" + sampling_hidden = sampling_info_md is None or sampling_info_md.strip() == "" + + + return options_dist_dcc, options_sampling_dcc, dist_info_md, sampling_info_md, dist_hidden, sampling_hidden + + + # optional manual input + @callback( + Output({"type": "sampling", "renderer": self.id, "index": MATCH, "manual": True}, "value"), + Output({"type": "manual_input-sampling", "renderer": self.id, "index": MATCH}, "value"), + Input({"type": "manual_input-sampling", "renderer": self.id, "index": MATCH}, "value"), + Input({"type": "sampling", "renderer": self.id, "index": MATCH, "manual": True}, "value"), + State("distribution-selector", "value"), + State(f"sampling-selector-{self.id}", "value"), + prevent_initial_call=True, + ) + def manual_input_changed(val, val_silder, selected_distribution, selected_sampling): + triggered_id = dash.ctx.triggered_id + source = triggered_id["type"] + + if val is None and source == "manual_input-sampling": + return no_update, no_update + + sampling_options = self.object.distributions[selected_distribution].sampling_method_dict[selected_sampling].sample_options + + try: + index = int(triggered_id["index"]) + except (TypeError, ValueError): + return no_update, no_update + if index < 0 or index >= len(sampling_options): + return no_update, no_update + + wrapper = sampling_options[index] + + if source == "manual_input-sampling": # manual input changed, update slider + + if (wrapper.check_input is None or wrapper.check_input(val)) and wrapper.slider.is_valid(val): + slider_value = wrapper.update_state_manual(val) + return slider_value, no_update + else: + return no_update, no_update + + else: # slider changed, update manual input + return no_update, wrapper.slider.transfrom_up(val_silder) + + @callback( + Output({"type": "dist", "renderer": self.id, "index": MATCH, "manual": True}, "value"), + Output({"type": "manual_input-dist", "renderer": self.id, "index": MATCH}, "value"), + Input({"type": "manual_input-dist", "renderer": self.id, "index": MATCH}, "value"), + Input({"type": "dist", "renderer": self.id, "index": MATCH, "manual": True}, "value"), + State("distribution-selector", "value"), + State(f"sampling-selector-{self.id}", "value"), + prevent_initial_call=True, + ) + def manual_input_dist_changed(val_manual, val_slider, selected_distribution, selected_sampling): + triggered_id = dash.ctx.triggered_id + source = triggered_id["type"] + + if val_manual is None and source == "manual_input-dist": + return no_update, no_update + + dist_options = self.object.distributions[selected_distribution].distribution_options + + try: + index = int(triggered_id["index"]) + except (TypeError, ValueError): + return no_update, no_update + if index < 0 or index >= len(dist_options): + return no_update, no_update + + wrapper = dist_options[index] + + if source == "manual_input-dist": + # check_input is given by distribution/ sampling method, if None, no special constraints are given + # slider.is_valid is given by the slider itself, can be less strict + if (wrapper.check_input is None or wrapper.check_input(val_manual)) and wrapper.slider.is_valid(val_manual): + slider_value = wrapper.update_state_manual(val_manual) + return slider_value, no_update + return no_update, no_update + + # slider changed, sync manual input display + return no_update, wrapper.slider.transfrom_up(val_slider) + + def _register_3d_plot_callbacks(self): + + # updates the plot based on selected sampling options + @callback( + Output(f"graph-{self.id}", "figure", allow_duplicate=True), + Input({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "value"), + State({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "id"), + Input({"type": "sampling", "renderer": self.id, "index": ALL, "manual": ALL}, "value"), + State({"type": "sampling", "renderer": self.id, "index": ALL, "manual": ALL}, "id"), + Input("distribution-selector", "value"), + Input(f"sampling-selector-{self.id}", "value"), + Input(f"distribution-options-{self.id}", "children"), + State(f"device-pixel-ratio-{self.id}", "data"), + prevent_initial_call='initial_duplicate' + ) + def update_plot_sample_callback(values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr): + return self.update_plot_sample(values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr) + + # updates the plot based on selected distribution options + @callback( + Output(f"graph-{self.id}", "figure", allow_duplicate=True), + Input({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "value"), + State({"type": "dist", "renderer": self.id, "index": ALL, "manual": ALL}, "id"), + Input("distribution-selector", "value"), + Input(f"sampling-selector-{self.id}", "value"), + Input(f"distribution-options-{self.id}", "children"), + prevent_initial_call='initial_duplicate' + ) + def update_plot_dist_callback(values_dist, ids_dist, selected_distribution, selected_sampling, _): + return self.update_plot_dist(values_dist, ids_dist, selected_distribution, selected_sampling, _) + + + + def update_plot_sample(self, values_dist, ids_dist, values_samp, ids_samp, selected_distribution, selected_sampling, _, dpr): + try: + dist_options = self.object.distributions[selected_distribution].distribution_options + sampling_options = self.object.distributions[selected_distribution].sampling_method_dict[selected_sampling].sample_options + except KeyError: + # got stale values, ignore + return dash.no_update + + # the order of options might not be guaranteed, so we map them by their ids + id_value_dist = [(id,v) for id, v in zip(ids_dist, values_dist)] + id_value_samp = [(id,v) for id, v in zip(ids_samp, values_samp)] + + + # and them sort them, so they are in the same order as sampling_options and dist_options + options_samp_new = sorted(id_value_samp, key=lambda x: int(x[0]["index"])) + options_dist_new = sorted(id_value_dist, key=lambda x: int(x[0]["index"])) + + + for opt, (id, new_state) in zip(sampling_options, options_samp_new): + opt.update_state(new_state) + + for opt, (id, new_state) in zip(dist_options, options_dist_new): + opt.update_state(new_state) + + # samples + samples, _ = self.object.update_sample(selected_distribution, selected_sampling, sampling_options, dist_options) + + + patched_figure = Patch() + + + patched_figure["data"][1].x = samples[:, 0] + patched_figure["data"][1].y = samples[:, 1] + patched_figure["data"][1].z = samples[:, 2] + + # set size based on number of samples + if samples.size == 0: + marker_size = 0 + else: + sample_count = samples.shape[0] + marker_size = (10 * (sample_count / 100) ** (-0.35)) / dpr + marker_size = np.minimum(10,marker_size) + + + patched_figure["data"][1].marker.size = marker_size + return patched_figure + + def update_plot_dist(self, values_dist, ids_dist, selected_distribution, selected_sampling, _): + dist_options = self.object.distributions[selected_distribution].distribution_options + + # the order of options might not be guaranteed, so we map them by their ids + id_value_dist = [(id,v) for id, v in zip(ids_dist, values_dist)] + + options_dist_new = sorted(id_value_dist, key=lambda x: int(x[0]["index"])) + + + for opt, (id, new_state) in zip(dist_options, options_dist_new): + opt.update_state(new_state) + + + # meshed density function plot plot + patched_figure = Patch() + + + pdf = self.object.distributions[selected_distribution].get_pdf(list(dist_options)) + if pdf is not None: + x, y, z = self.object.generate_mesh(pdf) + else: + x, y, z = [], [], [] + + patched_figure["data"][2].x = x + patched_figure["data"][2].y = y + patched_figure["data"][2].z = z + + return patched_figure + + + + + def get_layout_components(self): + initial_distribution = self.object.distributions[list(self.object.distributions.keys())[0]] + initial_sampling_method = initial_distribution.sampling_methods[0].get_name() if initial_distribution.sampling_methods else "no sampling methods found" + initial_sampling_options = [x.get_name() for x in initial_distribution.sampling_methods] + + options = [ + dcc.Store(id=f"device-pixel-ratio-{self.id}", data=1), + html.Br(), + html.P("Select Distribution and Sampling Method:"), + dcc.RadioItems( + id="distribution-selector", + options=(list(self.object.distributions.keys())), + value=initial_distribution.get_name() if self.object.distributions else "no distributions found", + ), + html.Br(), + dcc.RadioItems( + id=f"sampling-selector-{self.id}", + options=(list(initial_sampling_options)), + value=initial_sampling_method, + ), + html.Br(), + html.Hr(), + html.Br(), + + html.Div(id=f"distribution-options-{self.id}"), + html.Div(id=f"sampling-options-{self.id}"), + + html.Hr(id=f"distribution-info-divider-{self.id}", hidden=True), + + dcc.Markdown(id=f"distribution-info-markdown-{self.id}", mathjax=True), + + html.Hr(id=f"sampling-method-info-divider-{self.id}", hidden=True), + + dcc.Markdown(id=f"sampling-method-info-markdown-{self.id}", mathjax=True), + + + ] + + graph = [dcc.Graph(id=f"graph-{self.id}", figure=self.fig, config=self.config, style={'height': '100%'})] + + return options, graph diff --git a/renderer/plot_settings_2d.py b/renderer/plot_settings_2d.py new file mode 100644 index 0000000..c3e00d7 --- /dev/null +++ b/renderer/plot_settings_2d.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +import numpy as np + +@dataclass +class PlotSettings2D: + axes_2d_x: tuple + axes_2d_y: tuple + lock_aspect_ratio: bool + + periodic_x: bool = False + periodic_y: bool = False + periodic_x_amount: float = 0.0 # amount to shift for periodicity + periodic_y_amount: float = 0.0 + + x_title: str = "" # title for axis + y_title: str = "" + + reverse_x_y_axis: bool = False # if set to True in the (n,2) shaped data, first column is y and second is x + + color_location: tuple = (0,0,2*np.pi, 2*np.pi) # location of where pdf is mapped: (min_x, min_y, max_x, max_y) diff --git a/renderer/renderer.py b/renderer/renderer.py new file mode 100644 index 0000000..c17af90 --- /dev/null +++ b/renderer/renderer.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + +class Renderer(ABC): + + """ + returns a tuple of the a list of settings components (eg. for the sidebar) and a list of plot components + """ + @abstractmethod + def get_layout_components(self): + pass \ No newline at end of file diff --git a/renderer/selfcontained_distribution_renderer.py b/renderer/selfcontained_distribution_renderer.py new file mode 100644 index 0000000..7b7530d --- /dev/null +++ b/renderer/selfcontained_distribution_renderer.py @@ -0,0 +1,11 @@ +from model.selfcontained_distribution import SelfContainedDistribution + +class SelfContainedDistributionRenderer: + def __init__(self, distribution: SelfContainedDistribution): + self.distribution = distribution + + self.plot_layout = distribution.plot_layout + self.settings_layout = distribution.settings_layout + + def get_layout_components(self): + return (self.settings_layout, self.plot_layout) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index cbc3e63..d9eda04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,42 +1,68 @@ -blinker==1.7.0 -certifi==2024.2.2 -charset-normalizer==3.3.2 -click==8.1.7 -contourpy==1.2.1 -cycler==0.12.1 -dash==2.16.1 -dash-bootstrap-components==1.6.0 -dash-core-components==2.0.0 -dash-design-kit==0.0.1 -dash-html-components==2.0.0 -dash-table==5.0.0 -dash_mantine_components==0.14.2 -Flask==3.0.3 -fonttools==4.51.0 -gunicorn==22.0.0 -idna==3.7 -importlib_metadata==7.1.0 -itsdangerous==2.2.0 -Jinja2==3.1.3 -kiwisolver==1.4.5 -MarkupSafe==2.1.5 -matplotlib==3.8.4 -nest-asyncio==1.6.0 -numpy==1.26.4 -packaging==24.0 -pandas==2.2.2 -pillow==10.3.0 -plotly==5.21.0 -pyparsing==3.1.2 -python-dateutil==2.9.0.post0 -pytz==2024.1 -requests==2.31.0 -retrying==1.3.4 -scipy==1.13.0 -six==1.16.0 -tenacity==8.2.3 -typing_extensions==4.11.0 -tzdata==2024.1 -urllib3==2.2.1 -Werkzeug==3.0.2 -zipp==3.18.1 +astropy-iers-data==0.2025.11.3.0.38.37 ; python_version == "3.10" +astropy==6.1.7 ; python_version == "3.10" +beartype==0.22.5 ; python_version == "3.10" +blinker==1.9.0 ; python_version == "3.10" +certifi==2025.10.5 ; python_version == "3.10" +charset-normalizer==3.4.4 ; python_version == "3.10" +click==8.3.0 ; python_version == "3.10" +colorama==0.4.6 ; python_version == "3.10" and platform_system == "Windows" +contourpy==1.3.2 ; python_version == "3.10" +cycler==0.12.1 ; python_version == "3.10" +dash-bootstrap-components==1.7.1 ; python_version == "3.10" +dash-core-components==2.0.0 ; python_version == "3.10" +dash-html-components==2.0.0 ; python_version == "3.10" +dash-mantine-components==0.14.7 ; python_version == "3.10" +dash-resizable-panels==0.1.0 ; python_version == "3.10" +dash-table==5.0.0 ; python_version == "3.10" +dash-vtk==0.0.9 ; python_version == "3.10" +dash==2.18.2 ; python_version == "3.10" +deterministic-gaussian-sampling-fibonacci @ git+https://github.com/KIT-ISAS/deterministic_gaussian_sampling_fibonacci@fa97bc2793e87424fe56e208a5932a6cd6ec8ea2 ; python_version == "3.10" +et-xmlfile==2.0.0 ; python_version == "3.10" +filterpy==1.4.5 ; python_version == "3.10" +flask==3.0.3 ; python_version == "3.10" +fonttools==4.60.1 ; python_version == "3.10" +gunicorn==23.0.0 ; python_version == "3.10" +h5py==3.15.1 ; python_version == "3.10" +idna==3.11 ; python_version == "3.10" +importlib-metadata==8.7.0 ; python_version == "3.10" +itsdangerous==2.2.0 ; python_version == "3.10" +jinja2==3.1.6 ; python_version == "3.10" +kent-distribution @ git+https://github.com/Vlad-Kor/kent_distribution@1637440397d97c51ad688fdcc8e2c96c6db48d77 ; python_version == "3.10" +kiwisolver==1.4.9 ; python_version == "3.10" +markupsafe==3.0.3 ; python_version == "3.10" +matplotlib==3.10.7 ; python_version == "3.10" +mpmath==1.3.0 ; python_version == "3.10" +narwhals==2.10.2 ; python_version == "3.10" +nest-asyncio==1.6.0 ; python_version == "3.10" +numpy-quaternion==2024.0.12 ; python_version == "3.10" +numpy==2.2.6 ; python_version == "3.10" +openpyxl==3.1.5 ; python_version == "3.10" +packaging==25.0 ; python_version == "3.10" +pandas==2.3.3 ; python_version == "3.10" +pillow==12.0.0 ; python_version == "3.10" +platformdirs==4.5.0 ; python_version == "3.10" +plotly==6.4.0 ; python_version == "3.10" +pooch==1.8.2 ; python_version == "3.10" +pyerfa==2.0.1.5 ; python_version == "3.10" +pyparsing==3.2.5 ; python_version == "3.10" +pyrecest @ git+https://github.com/KIT-ISAS/pyRecEst.git@002c7757e2a5a81e4e57da5c1b657c5f9bd5cf6e ; python_version == "3.10" +pyshtools==4.13.1 ; python_version == "3.10" +python-dateutil==2.9.0.post0 ; python_version == "3.10" +pytz==2025.2 ; python_version == "3.10" +pyyaml==6.0.3 ; python_version == "3.10" +requests==2.32.5 ; python_version == "3.10" +retrying==1.4.2 ; python_version == "3.10" +scipy==1.15.3 ; python_version == "3.10" +setuptools==80.9.0 ; python_version == "3.10" +shapely==2.1.2 ; python_version == "3.10" +six==1.17.0 ; python_version == "3.10" +sphstat @ git+https://github.com/Vlad-Kor/sphstat@fad7f8779d4b4f8eeccae0f5e1e2ed6dd7280a22 ; python_version == "3.10" +sympy==1.14.0 ; python_version == "3.10" +tqdm==4.67.1 ; python_version == "3.10" +typing-extensions==4.15.0 ; python_version == "3.10" +tzdata==2025.2 ; python_version == "3.10" +urllib3==1.26.20 ; python_version == "3.10" +vtk==9.5.2 ; python_version == "3.10" +werkzeug==3.0.6 ; python_version == "3.10" +xarray==2025.6.1 ; python_version == "3.10" +zipp==3.23.0 ; python_version == "3.10" diff --git a/server_commands.txt b/server_commands.txt index 533cba7..a95c290 100644 --- a/server_commands.txt +++ b/server_commands.txt @@ -1,4 +1,3 @@ - # Create server https://portal.bw-cloud.org/project/instances/ https://www.bw-cloud.org/de/bwcloud_scope/nutzen @@ -30,6 +29,27 @@ nohup python app.py & nohup gunicorn --bind 0.0.0.0:8080 --workers 32 app:server & TODO use daemon +# install docker +sudo apt update +sudo apt install -y docker.io docker-compose +sudo systemctl enable --now docker +sudo usermod -aG docker $USER +(then exit ssh and log back in) + +# run with docker +('docker-compose' for old docker version from debian repos, 'docker compose' for newer versions like ubuntu-latest github runner) +docker-compose --profile default up -d --build + +run tests: +docker compose --profile test build tests +docker compose --profile test run --rm tests + +turn server off (not needed for updating image): +docker compose down + +clean up onld images: +docker image prune -a -f + # Test in Browser http://193.196.39.120:8080 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/end_to_end_test.py b/tests/end_to_end_test.py new file mode 100644 index 0000000..6f4b9d3 --- /dev/null +++ b/tests/end_to_end_test.py @@ -0,0 +1,41 @@ +import dash +from app import app +import pytest + +PAGES_WITH_PLOTLY_PLOTS = ['/conditional', '/gauss1d', '/gauss2d', '/sphere', '/torus'] +PAGES_WITH_AT_LEAST_ONE_SLIDER = ['/conditional', '/gauss1d', '/gauss2d', '/sphere', '/torus'] + +# basic launch web-app test +def test_001_home_end_to_end(dash_duo): + dash_duo.start_server(app) + + assert dash_duo.get_logs() == [], "browser console should contain no error" + + dash_duo.wait_for_text_to_equal("h1", "ISAS Interactive", timeout=10) + +@pytest.mark.parametrize("path", PAGES_WITH_PLOTLY_PLOTS) +def test_002_page_loads_plots(dash_duo, path): + dash_duo.start_server(app) + + # navbutton should appear + link = dash_duo.wait_for_element(f'a.nav-link[href="{path}"]', timeout=10) + link.click() + + # a plot should appear + dash_duo.wait_for_element("div.js-plotly-plot", timeout=10) + + assert dash_duo.get_logs() == [], "browser console should contain no error" + + +@pytest.mark.parametrize("path", PAGES_WITH_PLOTLY_PLOTS) +def test_003_has_one_slider(dash_duo, path): + dash_duo.start_server(app) + + # navbutton should appear + link = dash_duo.wait_for_element(f'a.nav-link[href="{path}"]', timeout=10) + link.click() + + # a slider should appear + dash_duo.wait_for_element("div.rc-slider", timeout=10) + + assert dash_duo.get_logs() == [], "browser console should contain no error" \ No newline at end of file diff --git a/util/cartesian_util.py b/util/cartesian_util.py new file mode 100644 index 0000000..bbd21e6 --- /dev/null +++ b/util/cartesian_util.py @@ -0,0 +1,20 @@ +import numpy as np + +class CartesianUtil: + + """ Generate a Cartesian grid of samples. + n: number of samples along one dimension (total samples = n*n) + widths: tuple of widths along each dimension + Returns: array of shape (n*n, 2) with samples + """ + @staticmethod + def generate_cartesian_grid(n, widths): + indices = np.arange(n) + + # use cell centers of an n x n grid on [0, 1) x [0, 1), then scale to widths + t = (indices + 0.5) / n * widths[0] + p = (indices + 0.5) / n * widths[1] + + t_grid, p_grid = np.meshgrid(t, p, indexing="ij") + + return np.column_stack((t_grid.ravel(), p_grid.ravel())) \ No newline at end of file diff --git a/util/gaus_util.py b/util/gaus_util.py new file mode 100644 index 0000000..62c54aa --- /dev/null +++ b/util/gaus_util.py @@ -0,0 +1,38 @@ +import numpy as np +from scipy.stats import norm +from deterministic_gaussian_sampling_fibonacci import get_uniform_grid + + +class GausUtil: + @staticmethod + def transform_grid_gaussian(grid, mu, cov): + eps = 1e-9 + grid = np.clip(grid, eps, 1 - eps) # avoid inf in ppf + + gaus = norm.ppf(grid) + + var = np.mean(gaus**2, axis=0) + + gaus = gaus / np.sqrt(var) + + # scale with eigen decomposition + ew, V = np.linalg.eig(cov) + + D = np.diag(np.sqrt(ew)) + + gaus = gaus.T # (2,L) + + gaus = V @ D @ gaus # (2,2) @ (2,2) @ (2,L) -> (2,L) + + gaus = gaus.T # (L,2) + + gaus[:,0] += mu[0] + gaus[:,1] += mu[1] + + return gaus + + @staticmethod + def sample_frolov_gaussian(mu, cov, sample_count, variant="ClassicalFrolov"): + grid = get_uniform_grid(2, sample_count, variant) + gaus_grid = GausUtil.transform_grid_gaussian(grid, mu, cov) + return gaus_grid \ No newline at end of file diff --git a/util/selectors/__init__.py b/util/selectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/util/selectors/selector.py b/util/selectors/selector.py new file mode 100644 index 0000000..4259ba8 --- /dev/null +++ b/util/selectors/selector.py @@ -0,0 +1,27 @@ +from abc import ABC, abstractmethod + +class Selector(ABC): + def __init__(self): + # this is the id of the last time the to_dash_component method was called + # to_dash_component must have been called at least once for this to be set + self.id = None + + # has the sideeffect of updating self.id + @abstractmethod + def to_dash_component(self, _type, id, renderer_id): + pass + + def transfrom_down(self, x): + # identity by default, override in subclasses if needed + # does need to be defined if dash slider_values do not correspond to self.state + return x + + def transfrom_up(self, x): + # identity by default, override in subclasses if needed + # does need to be defined if dash slider_values do not correspond to self.state + return x + + def is_valid(self, x): + # by default all values are valid + # override in subclasses if needed + return True diff --git a/util/selectors/silder_log.py b/util/selectors/silder_log.py new file mode 100644 index 0000000..a16d83f --- /dev/null +++ b/util/selectors/silder_log.py @@ -0,0 +1,85 @@ +from dash import dcc, html +import numpy as np +from util.selectors.selector import Selector + +SLIDER_OPT_AMOUNT = 30 +SLIDER_MARK_AMOUNT = 5 + +class LogSlider(Selector): + def __init__(self, name, min, state, max, custom_constraints=id): + self.name = name + self.min = min + self.state = state + self.max = max + + self.id = None + + self.log_max = np.log10(self.max) + self.log_min = np.log10(self.min) + + + def calculate_step(self): + step = (self.log_max - self.log_min) / SLIDER_OPT_AMOUNT + return step + + def calculate_marks(self): + marks = {} + step = (self.log_max - self.log_min) / SLIDER_MARK_AMOUNT + + for i in range(SLIDER_MARK_AMOUNT + 1): + position = self.log_min + i * step + + if position % 1 == 0: # it doesnt like float keys for "integer" values + position = int(position) + else: + position = float(position) + + value = int(self.transfrom_up(position)) if float(self.transfrom_up(position)).is_integer() else float(self.transfrom_up(position)) + marks[position] = f"{value:.6g}" # 6 significant digits + return marks + + @staticmethod + def round_nice_number(x): + # rounds to "nice" number with less than ~5% error + if x == 0: + return 0 + sign = np.sign(x) + x = abs(x) + + # step ~10% of x => ~5% rounding error + step = (10 ** np.floor(np.log10(x))) / 10 + nice = sign * round(x / step) * step + + return int(nice) if float(nice).is_integer() else nice + + # same as transform_pow_10 in tooltip.js + def transfrom_up(self, value): + return LogSlider.round_nice_number(10 ** value) + + # not quite the inverse of transfrom_up due to rounding + def transfrom_down(self, value): + if value <= 0: + return 0 + return np.log10(value) + + def to_dash_component(self, _type, id, renderer_id, manual=False): + self.id = id + slider_id = {"type": _type, "index": id, "renderer": renderer_id, "manual": manual} + + return html.Div([ + html.Label(self.name), + + dcc.Slider( + id=slider_id, + min=self.log_min, + max=self.log_max, + value=self.transfrom_down(self.state), + tooltip={"placement": "bottom", "always_visible": True, "transform": "transform_log_nice"}, + step=self.calculate_step(), + marks=self.calculate_marks(), + updatemode="drag", + ) + ]) + + def update_state(self, new_state): + self.state = self.transfrom_up(new_state) diff --git a/util/selectors/silder_manual_input_wrapper.py b/util/selectors/silder_manual_input_wrapper.py new file mode 100644 index 0000000..9d2f5dd --- /dev/null +++ b/util/selectors/silder_manual_input_wrapper.py @@ -0,0 +1,53 @@ +from util.selectors.selector import Selector +from dash import dcc, html, callback, Input, Output, ALL + + +class SliderManualInputWrapper(): + def __init__(self, slider, check_input=None): + self.id = None # unique id for dash callback + self.slider = slider + self.check_input = check_input + + def to_dash_component(self, _type, id, renderer_id): + slider_component = self.slider.to_dash_component(_type, id, renderer_id, manual=True) + self.id = id + self.renderer_id = renderer_id + + component = html.Div([ + html.Div( + children=[slider_component], + style={"display": "inline-block", "width": r"calc(100% - 5rem)", "verticalAlign": "bottom"} + ), + html.Div( + children=[dcc.Input( + id={"type": f"manual_input-{_type}", "index": id, "renderer": renderer_id}, + type="number", + value=self.slider.state, + style={"width": "100%"}, + )], + style={"display": "inline-block", "width": "5rem", "verticalAlign": "bottom", "margin-bottom": "1rem"} + ), + + ]) + return component + + def update_state(self, new_state): # only called by slider callback, gauranteed valid + self.slider.update_state(new_state) + + def update_state_manual(self, manual_value): + #Update from a manual text/number input; converts to slider domain if needed.""" + if hasattr(self.slider, "transfrom_down"): + slider_value = self.slider.transfrom_down(manual_value) + else: + slider_value = manual_value + + self.slider.update_state(slider_value) + return slider_value + + @property + def state(self): + return self.slider.state + + @property + def idx(self): + return self.slider.idx diff --git a/util/selectors/slider.py b/util/selectors/slider.py new file mode 100644 index 0000000..da35230 --- /dev/null +++ b/util/selectors/slider.py @@ -0,0 +1,71 @@ +from dash import dcc, html +from util.selectors.selector import Selector + +SLIDER_OPT_AMOUNT = 30 +SLIDER_MARK_AMOUNT = 5 + +class Slider(Selector): + def __init__(self, name, min, state, max, custom_constraints=id): + self.name = name + self.min = min + self.state = state + self.max = max + + self.id = None + + + def calculate_step(self): + # rounds down to the neares human readable step + # eg. 1,5,25,50,100,250,500,1000, ... + + step_unrounded = (self.max - self.min) // SLIDER_OPT_AMOUNT + sequence = [1, 5, 25, 50] + + prev = 1 + + while True: + for s in sequence: + if step_unrounded < s: + return prev + prev = s + sequence = [s * 10 for s in sequence] + + def calculate_marks(self): + def round_nice_number(x): + if x >= 1000: + return round(x, -3) + elif x >= 100: + return round(x, -2) + else: + return x + + marks = {} + step = (self.max - self.min) / SLIDER_MARK_AMOUNT + for i in range(SLIDER_MARK_AMOUNT + 1): + value = int(self.min + i * step) + value = round_nice_number(value) + marks[value] = str(value) + return marks + + + def to_dash_component(self, _type, id, renderer_id, manual=False): + self.id = id + slider_id = {"type": _type, "index": id, "renderer": renderer_id, "manual": manual} + + return html.Div([ + html.Label(self.name), + + dcc.Slider( + id=slider_id, + min=self.min, + max=self.max, + value=self.state, + tooltip={"placement": "bottom", "always_visible": True}, + step=self.calculate_step(), + marks=self.calculate_marks(), + updatemode="drag", + ) + ]) + + def update_state(self, new_state): + self.state = int(new_state) diff --git a/util/selectors/slider_fib.py b/util/selectors/slider_fib.py new file mode 100644 index 0000000..2e7e326 --- /dev/null +++ b/util/selectors/slider_fib.py @@ -0,0 +1,89 @@ +from dash import dcc, html +import numpy as np +from util.selectors.selector import Selector +import sympy as sp + + +SLIDER_MARK_AMOUNT = 5 +""" +Silder that only selects Fibonacci numbers within a given range. +min and max are the indices in the Fibonacci sequence. +state is not an index, but the actual Fibonacci number at a valid index. +if minus_1 is set, Fibonacci numbers -1 are used instead. (initial) state is should then also be Fibonacci number -1. +""" +class SliderFib(Selector): + def __init__(self, name, min, state, max, idx, minus_1=False): + self.name = name + self.min = min + self.state = state + self.idx = idx + self.max = max + + self.id = None + self.minus_1 = minus_1 + + def to_dash_component(self, _type, id, renderer_id, manual=False): + self.id = id + slider_id = {"type": _type, "index": id, "renderer": renderer_id, "manual": manual} + + return html.Div([ + html.Label(self.name), + + dcc.Slider( + id=slider_id, + min=self.min, + max=self.max, + value=self.idx, + tooltip={"placement": "bottom", "always_visible": True, "transform": "transform_fib" if not self.minus_1 else "transform_fib_m1"}, + step=1, + marks=self.calculate_marks(), + updatemode="drag", + ) + ]) + + def calculate_marks(self): + + marks = {} + step = (self.max - self.min) / SLIDER_MARK_AMOUNT + for i in range(SLIDER_MARK_AMOUNT + 1): + value = int(self.min + i * step) + if not self.minus_1: + marks[value] = str(sp.fibonacci(value)) + else: + marks[value] = str(sp.fibonacci(value) - 1) + return marks + + + def update_state(self, new_state): + self.state = int(sp.fibonacci(new_state) - (1 if self.minus_1 else 0)) + self.idx = int(new_state) + + def transfrom_up(self, x): + return int(sp.fibonacci(x) - (1 if self.minus_1 else 0)) + + def transfrom_down(self, x): + if self.minus_1: + x = x + 1 + + for i in range(0, x + 3): + if int(sp.fibonacci(i)) == x: + return i + raise ValueError(f"{x} is not a Fibonacci number") + + def is_valid(self, n): + if self.minus_1: + n = n + 1 + + if n < 0: + return False + # A number is a Fibonacci number if and only if one or both of (5*n^2 + 4) or (5*n^2 - 4) is a perfect square + # https://en.wikipedia.org/wiki/Fibonacci_sequence + test1 = 5 * n * n + 4 + test2 = 5 * n * n - 4 + + def is_perfect_square(x): + s = int(np.sqrt(x)) + return s * s == x + return is_perfect_square(test1) or is_perfect_square(test2) + + diff --git a/util/selectors/slider_float.py b/util/selectors/slider_float.py new file mode 100644 index 0000000..c5fef61 --- /dev/null +++ b/util/selectors/slider_float.py @@ -0,0 +1,41 @@ +from dash import dcc, html +from util.selectors.selector import Selector + +SLIDER_OPT_AMOUNT = 100 +SLIDER_MARK_AMOUNT = 5 + +class FloatSlider(Selector): + def __init__(self, name, min, state, max, transform_tooltip=None): + self.name = name + self.min = min + self.state = state + self.max = max + + self.id = None + self.transform_tooltip = transform_tooltip + + + def to_dash_component(self, _type, id, renderer_id, manual=False): + if self.transform_tooltip is None: + tooltip = {"placement": "bottom", "always_visible": True} + else: + tooltip = {"placement": "bottom", "always_visible": True, "transform": self.transform_tooltip} + + self.id = id + slider_id = {"type": _type, "index": id, "renderer": renderer_id, "manual": manual} + + return html.Div([ + html.Label(self.name), + + dcc.Slider( + id=slider_id, + min=self.min, + max=self.max, + value=self.state, + tooltip=tooltip, + updatemode="drag", + ) + ]) + + def update_state(self, new_state): + self.state = new_state diff --git a/util/selectors/slider_pi.py b/util/selectors/slider_pi.py new file mode 100644 index 0000000..5dbcfa1 --- /dev/null +++ b/util/selectors/slider_pi.py @@ -0,0 +1,101 @@ +from dash import dcc, html +from util.selectors.selector import Selector +import numpy as np + +SLIDER_OPT_AMOUNT = 100 +SLIDER_MARK_AMOUNT = 5 + +class PiSlider(Selector): + """ + Slider for values between 0 and 2pi or pi., like FloatSlider but with pi notation on marks. + min, state, max shall be given relative to pi, eg. min=0, max=2 for 0 to 2pi + """ + + def __init__(self, name, min, state, max, transform_tooltip=None): + self.name = name + self.min = min + self.state = state * np.pi + self.max = max + + self.id = None + self.transform_tooltip = transform_tooltip + + def calculate_marks(self): + # for small marks + if self.max == 2 and self.min == 0: + return { + 0: "0", + 0.5: "½π", + 1: "π", + 1.5: "3⁄2π", + 2: "2π" + } + if self.max == 1 and self.min == 0: + return { + 0: "0", + 0.25: "¼π", + 0.5: "½π", + 0.75: "¾π", + 1: "π" + } + + + # for bigger values of pi use clean integer values + def round_nice_number(x): + if x >= 1000: + return round(x, -3) + elif x >= 100: + return round(x, -2) + else: + return x + + marks = {} + step = (self.max - self.min) / SLIDER_MARK_AMOUNT + for i in range(SLIDER_MARK_AMOUNT + 1): + value = int(self.min + i * step) + value = round_nice_number(value) + marks[value] = str(value) + "π" + return marks + + + def to_dash_component(self, _type, id, renderer_id, manual=False): + if self.transform_tooltip is None: + tooltip = { + "placement": "bottom", + "always_visible": True, + "template": "{value}π" + } + else: + tooltip = { + "placement": "bottom", + "always_visible": True, + "transform": self.transform_tooltip, + "template": "{value}π" + } + + self.id = id + slider_id = {"type": _type, "index": id, "renderer": renderer_id, "manual": manual} + + return html.Div([ + html.Label(self.name), + + dcc.Slider( + id=slider_id, + min=self.min, + max=self.max, + value=self.transfrom_down(self.state), + tooltip=tooltip, + updatemode="drag", + marks=self.calculate_marks(), + ) + ]) + + def update_state(self, new_state): + self.state = self.transfrom_up(new_state) + + + def transfrom_up(self, x): + return x * np.pi + + def transfrom_down(self, x): + return x / np.pi diff --git a/util/selectors/slider_square.py b/util/selectors/slider_square.py new file mode 100644 index 0000000..0597425 --- /dev/null +++ b/util/selectors/slider_square.py @@ -0,0 +1,70 @@ +from dash import dcc, html +import numpy as np +from util.selectors.selector import Selector +import sympy as sp + + +SLIDER_MARK_AMOUNT = 5 +""" +Silder that only selects perfect squares within a given range. +min and max are the indices of the square (eg. i^2). +state is not an index, but the actual Square number at a valid index. +""" +class SliderSquare(Selector): + def __init__(self, name, min, state, max, idx): + self.name = name + self.min = min + self.state = state + self.idx = idx + self.max = max + + self.id = None + + def to_dash_component(self, _type, id, renderer_id, manual=False): + self.id = id + slider_id = {"type": _type, "index": id, "renderer": renderer_id, "manual": manual} + + return html.Div([ + html.Label(self.name), + + dcc.Slider( + id=slider_id, + min=self.min, + max=self.max, + value=self.idx, + tooltip={"placement": "bottom", "always_visible": True, "transform": "transform_square"}, + step=1, + marks=self.calculate_marks(), + updatemode="drag", + ) + ]) + + def calculate_marks(self): + + marks = {} + step = (self.max - self.min) / SLIDER_MARK_AMOUNT + for i in range(SLIDER_MARK_AMOUNT + 1): + value = int(self.min + i * step) + marks[value] = str((value**2)) + return marks + + + def update_state(self, new_state): + self.state = int((new_state**2)) + self.idx = int(new_state) + + def transfrom_up(self, x): + return int((x**2)) + + def transfrom_down(self, x): + for i in range(0, int(np.sqrt(x)) + 1): + if (i**2) == x: + return i + raise ValueError(f"{x} is not a perfect square number") + + def is_valid(self, x): + if x < 0: + return False + + s = int(np.sqrt(x)) + return s * s == x