From 2016505b051e79f371f6b8d43bf21584a6806e49 Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Tue, 30 Dec 2025 11:40:03 -0800 Subject: [PATCH 01/22] add persistence across session and responsive layout --- crystal_toolkit/components/phonon.py | 84 +++++++++++++++++++++------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 5ef76d90..628106b6 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -26,6 +26,8 @@ from crystal_toolkit.helpers.layouts import Column, Columns, Label, get_data_list from crystal_toolkit.helpers.pretty_labels import pretty_labels +import json + if TYPE_CHECKING: from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine from pymatgen.electronic_structure.dos import CompleteDos @@ -37,6 +39,16 @@ MAX_MAGNITUDE = 300 MIN_MAGNITUDE = 0 +class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + if isinstance(obj, np.integer): + return int(obj) + if isinstance(obj, np.floating): + return float(obj) + return super().default(obj) + # TODOs: # - look for additional projection methods in phonon DOS (currently only atom # projections supported) @@ -80,12 +92,18 @@ def _sub_layouts(self) -> dict[str, Component]: fig = PhononBandstructureAndDosComponent.get_figure(None, None) # Main plot - graph = dcc.Graph( - figure=fig, - config={"displayModeBar": False}, - responsive=False, - id=self.id("ph-bsdos-graph"), + graph = html.Div( + [ + dcc.Graph( + figure=fig, + config={"displayModeBar": False}, + responsive=True, + id=self.id("ph-bsdos-graph"), + style={"height": "520px"} + ) + ] ) + # Brillouin zone zone_scene = self.get_brillouin_zone_scene(None) @@ -153,12 +171,24 @@ def _sub_layouts(self) -> dict[str, Component]: summary_dict = self._get_data_list_dict(None, None) summary_table = get_data_list(summary_dict) - # crystal visualization - - tip = html.H5( - "💡 Tips: Click different q-points and bands in the dispersion diagram to see the crystal vibration!", + tip = html.Div( + html.Span( + "💡 Tips: Click different q-points and bands in the dispersion diagram to see the crystal vibration!", + style={ + "border": "0.5px dashed black", + "display": "inline-flex", + "alignItems": "center", + "justifyContent": "center", + "textAlign": "center", + } + ), + style={ + "display": "flex", + "justifyContent": "center", + } ) - + + # crystal visualization crystal_animation = html.Div( CrystalToolkitAnimationScene( data={}, @@ -167,16 +197,16 @@ def _sub_layouts(self) -> dict[str, Component]: settings={"defaultZoom": 1.2}, axisView="SW", showControls=False, # disable download for now - ), - style={"width": "60%"}, + ) ) crystal_animation_controls = html.Div( [ html.Br(), - html.Div(tip, style={"textAlign": "center"}), + html.Br(), html.Br(), html.H5("Control Panel", style={"textAlign": "center"}), + html.Br(), html.H6("Supercell modification"), html.Br(), html.Div( @@ -184,6 +214,7 @@ def _sub_layouts(self) -> dict[str, Component]: self.get_numerical_input( kwarg_label="scale-x", default=1, + persistence_type="session", is_int=True, label="x", min=1, @@ -192,6 +223,7 @@ def _sub_layouts(self) -> dict[str, Component]: self.get_numerical_input( kwarg_label="scale-y", default=1, + persistence_type="session", is_int=True, label="y", min=1, @@ -200,18 +232,28 @@ def _sub_layouts(self) -> dict[str, Component]: self.get_numerical_input( kwarg_label="scale-z", default=1, + persistence_type="session", is_int=True, label="z", min=1, style={"width": "5rem"}, ), - html.Button( - "Update", - id=self.id("supercell-controls-btn"), - style={"height": "40px"}, - ), + html.Div( + html.Button( + "Update", + id=self.id("supercell-controls-btn"), + style={"height": "40px"}, + ), + style={ + "textAlign": "center", + "width": "100%" + } + ) + ], - style={"display": "flex"}, + style={ + "display": "flex" + }, ), html.Br(), html.Div( @@ -224,6 +266,7 @@ def _sub_layouts(self) -> dict[str, Component]: ) ), ], + style={"width": "100%"} ) return { @@ -244,6 +287,7 @@ def _get_animation_panel(self): [ Column( [ + sub_layouts["tip"], Columns( [ sub_layouts["crystal-animation"], @@ -252,7 +296,7 @@ def _get_animation_panel(self): ) ] ), - ] + ], ) def layout(self) -> html.Div: From 60bbdedb18159855a0502956e6de48b6b1639e0c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:44:39 +0000 Subject: [PATCH 02/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 32 +++++++++++----------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 628106b6..99e87841 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +import json from copy import deepcopy from typing import TYPE_CHECKING, Any @@ -26,8 +27,6 @@ from crystal_toolkit.helpers.layouts import Column, Columns, Label, get_data_list from crystal_toolkit.helpers.pretty_labels import pretty_labels -import json - if TYPE_CHECKING: from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine from pymatgen.electronic_structure.dos import CompleteDos @@ -39,6 +38,7 @@ MAX_MAGNITUDE = 300 MIN_MAGNITUDE = 0 + class NumpyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, np.ndarray): @@ -49,6 +49,7 @@ def default(self, obj): return float(obj) return super().default(obj) + # TODOs: # - look for additional projection methods in phonon DOS (currently only atom # projections supported) @@ -99,11 +100,10 @@ def _sub_layouts(self) -> dict[str, Component]: config={"displayModeBar": False}, responsive=True, id=self.id("ph-bsdos-graph"), - style={"height": "520px"} + style={"height": "520px"}, ) ] ) - # Brillouin zone zone_scene = self.get_brillouin_zone_scene(None) @@ -180,14 +180,14 @@ def _sub_layouts(self) -> dict[str, Component]: "alignItems": "center", "justifyContent": "center", "textAlign": "center", - } - ), + }, + ), style={ "display": "flex", "justifyContent": "center", - } + }, ) - + # crystal visualization crystal_animation = html.Div( CrystalToolkitAnimationScene( @@ -244,16 +244,10 @@ def _sub_layouts(self) -> dict[str, Component]: id=self.id("supercell-controls-btn"), style={"height": "40px"}, ), - style={ - "textAlign": "center", - "width": "100%" - } - ) - + style={"textAlign": "center", "width": "100%"}, + ), ], - style={ - "display": "flex" - }, + style={"display": "flex"}, ), html.Br(), html.Div( @@ -266,7 +260,7 @@ def _sub_layouts(self) -> dict[str, Component]: ) ), ], - style={"width": "100%"} + style={"width": "100%"}, ) return { @@ -293,7 +287,7 @@ def _get_animation_panel(self): sub_layouts["crystal-animation"], sub_layouts["crystal-animation-controls"], ] - ) + ), ] ), ], From 302e60c0a2b7db642042b6c35cd074468d0d0c4d Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Tue, 30 Dec 2025 11:45:13 -0800 Subject: [PATCH 03/22] remove debug object --- crystal_toolkit/components/phonon.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 628106b6..0adda7d5 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -39,16 +39,6 @@ MAX_MAGNITUDE = 300 MIN_MAGNITUDE = 0 -class NumpyEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, np.ndarray): - return obj.tolist() - if isinstance(obj, np.integer): - return int(obj) - if isinstance(obj, np.floating): - return float(obj) - return super().default(obj) - # TODOs: # - look for additional projection methods in phonon DOS (currently only atom # projections supported) From 50f2485abfab586fe6947426aaed93c6c3c75fcb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 22:53:15 +0000 Subject: [PATCH 04/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 1 - 1 file changed, 1 deletion(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 8e6350d9..7a3ed5f5 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -1,7 +1,6 @@ from __future__ import annotations import itertools -import json from copy import deepcopy from typing import TYPE_CHECKING, Any From 537efdf7f6cf1e1c3c0adf267428e5956c2fefd8 Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Fri, 12 Dec 2025 17:34:57 -0800 Subject: [PATCH 05/22] add ipython to pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ce07e026..1be35db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "scikit-learn", "shapely", "webcolors", + "ipython" ] [project.optional-dependencies] From 20d02c574e82606a0c89e5d606482ae8495c239b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:09:25 +0000 Subject: [PATCH 06/22] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.3 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.10) - [github.com/kynan/nbstripout: 0.8.1 → 0.8.2](https://github.com/kynan/nbstripout/compare/0.8.1...0.8.2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8eb470cb..183bf7db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ exclude: ^(docs/.+|.*lock.*|jupyterlab-extension/.+|.*\.(svg|js|css))|_version.p repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.10 hooks: - id: ruff args: [--fix, --ignore, D] @@ -40,7 +40,7 @@ repos: args: [--ignore-words-list, "nd,te,ois,dscribe", --check-filenames] - repo: https://github.com/kynan/nbstripout - rev: 0.8.1 + rev: 0.8.2 hooks: - id: nbstripout args: [--drop-empty-cells, --keep-output] From 3f34bd480f4ea144dd596fe3c5fb485dae645a0b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 22 Jan 2026 21:39:18 +0000 Subject: [PATCH 07/22] auto dependency upgrades --- requirements/ubuntu-latest_py3.11.txt | 119 +++++++---- requirements/ubuntu-latest_py3.11_extras.txt | 211 +++++++++---------- requirements/ubuntu-latest_py3.12.txt | 118 +++++++---- requirements/ubuntu-latest_py3.12_extras.txt | 211 +++++++++---------- 4 files changed, 359 insertions(+), 300 deletions(-) diff --git a/requirements/ubuntu-latest_py3.11.txt b/requirements/ubuntu-latest_py3.11.txt index fdf67f47..0a15a678 100644 --- a/requirements/ubuntu-latest_py3.11.txt +++ b/requirements/ubuntu-latest_py3.11.txt @@ -6,61 +6,73 @@ # annotated-types==0.7.0 # via pydantic +asttokens==3.0.1 + # via stack-data bibtexparser==1.4.3 # via pymatgen blake3==1.0.8 # via emmet-core blinker==1.9.0 # via flask -boto3==1.40.66 +boto3==1.42.33 # via mp-api -botocore==1.40.66 +botocore==1.42.33 # via # boto3 # s3transfer cachelib==0.13.0 # via flask-caching -certifi==2025.10.5 +certifi==2026.1.4 # via requests charset-normalizer==3.4.4 # via requests -click==8.3.0 +click==8.3.1 # via flask contourpy==1.3.3 # via matplotlib cycler==0.12.1 # via matplotlib -dash==3.2.0 +dash==3.4.0 # via crystal_toolkit (pyproject.toml) dash-mp-components==0.5.0rc0 # via crystal_toolkit (pyproject.toml) -emmet-core==0.86.0rc1 +decorator==5.2.1 + # via ipython +emmet-core==0.86.2 # via mp-api +executing==2.2.1 + # via stack-data flask==3.1.2 # via # dash # flask-caching flask-caching==2.3.1 # via crystal_toolkit (pyproject.toml) -fonttools==4.60.1 +fonttools==4.61.1 # via matplotlib -frozendict==2.4.6 +frozendict==2.4.7 # via crystal_toolkit (pyproject.toml) idna==3.11 # via requests imageio==2.37.2 # via scikit-image -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 # via dash +ipython==9.9.0 + # via crystal_toolkit (pyproject.toml) +ipython-pygments-lexers==1.1.1 + # via ipython itsdangerous==2.2.0 # via flask +jedi==0.19.2 + # via ipython jinja2==3.1.6 # via flask -jmespath==1.0.1 +jmespath==1.1.0 # via # boto3 # botocore -joblib==1.5.2 +joblib==1.5.3 # via # pymatgen # scikit-learn @@ -75,28 +87,30 @@ markupsafe==3.0.3 # flask # jinja2 # werkzeug -matplotlib==3.10.7 +matplotlib==3.10.8 # via pymatgen +matplotlib-inline==0.2.1 + # via ipython monty==2025.3.3 # via # emmet-core # mp-api # pymatgen -mp-api==0.45.12 +mp-api==0.45.15 # via crystal_toolkit (pyproject.toml) mpmath==1.3.0 # via sympy msgpack==1.1.2 # via mp-api -narwhals==2.10.2 +narwhals==2.15.0 # via plotly nest-asyncio==1.6.0 # via dash -networkx==3.5 +networkx==3.6.1 # via # pymatgen # scikit-image -numpy==2.3.4 +numpy==2.4.1 # via # contourpy # imageio @@ -111,9 +125,11 @@ numpy==2.3.4 # shapely # spglib # tifffile -orjson==3.11.4 - # via pymatgen -packaging==25.0 +orjson==3.11.5 + # via + # mp-api + # pymatgen +packaging==26.0 # via # lazy-loader # matplotlib @@ -121,31 +137,45 @@ packaging==25.0 # scikit-image palettable==3.3.3 # via pymatgen -pandas==2.3.3 +pandas==3.0.0 # via pymatgen -pillow==12.0.0 +parso==0.8.5 + # via jedi +pexpect==4.9.0 + # via ipython +pillow==12.1.0 # via # imageio # matplotlib # scikit-image -plotly==6.4.0 +plotly==6.5.2 # via # dash # pymatgen +prompt-toolkit==3.0.52 + # via ipython +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data pybtex==0.25.1 # via emmet-core -pydantic==2.12.3 +pydantic==2.12.5 # via # emmet-core # pydantic-settings # pymatgen-io-validation -pydantic-core==2.41.4 +pydantic-core==2.41.5 # via pydantic -pydantic-settings==2.11.0 +pydantic-settings==2.12.0 # via # crystal_toolkit (pyproject.toml) # emmet-core # pymatgen-io-validation +pygments==2.19.2 + # via + # ipython + # ipython-pygments-lexers pymatgen==2025.10.7 # via # crystal_toolkit (pyproject.toml) @@ -154,7 +184,7 @@ pymatgen==2025.10.7 # pymatgen-io-validation pymatgen-io-validation==0.1.2 # via emmet-core -pyparsing==3.2.5 +pyparsing==3.3.2 # via # bibtexparser # matplotlib @@ -165,8 +195,6 @@ python-dateutil==2.9.0.post0 # pandas python-dotenv==1.2.1 # via pydantic-settings -pytz==2025.2 - # via pandas pyyaml==6.0.3 # via pybtex requests==2.32.5 @@ -177,19 +205,17 @@ requests==2.32.5 # pymatgen-io-validation retrying==1.4.2 # via dash -ruamel-yaml==0.18.16 +ruamel-yaml==0.19.1 # via # monty # pymatgen -ruamel-yaml-clib==0.2.14 - # via ruamel-yaml -s3transfer==0.14.0 +s3transfer==0.16.0 # via boto3 -scikit-image==0.25.2 +scikit-image==0.26.0 # via crystal_toolkit (pyproject.toml) -scikit-learn==1.7.2 +scikit-learn==1.8.0 # via crystal_toolkit (pyproject.toml) -scipy==1.16.3 +scipy==1.17.0 # via # pymatgen # scikit-image @@ -198,25 +224,32 @@ shapely==2.1.2 # via crystal_toolkit (pyproject.toml) six==1.17.0 # via python-dateutil -smart-open==7.4.4 +smart-open==7.5.0 # via mp-api -spglib==2.6.0 +spglib==2.7.0 # via pymatgen +stack-data==0.6.3 + # via ipython sympy==1.14.0 # via pymatgen tabulate==0.9.0 # via pymatgen threadpoolctl==3.6.0 # via scikit-learn -tifffile==2025.10.16 +tifffile==2026.1.14 # via scikit-image tqdm==4.67.1 # via pymatgen +traitlets==5.14.3 + # via + # ipython + # matplotlib-inline typing-extensions==4.15.0 # via # blake3 # dash # emmet-core + # ipython # mp-api # pydantic # pydantic-core @@ -226,21 +259,21 @@ typing-inspection==0.4.2 # via # pydantic # pydantic-settings -tzdata==2025.2 - # via pandas uncertainties==3.2.3 # via pymatgen -urllib3==2.5.0 +urllib3==2.6.3 # via # botocore # requests +wcwidth==0.3.0 + # via prompt-toolkit webcolors==25.10.0 # via crystal_toolkit (pyproject.toml) -werkzeug==3.1.3 +werkzeug==3.1.5 # via # dash # flask -wrapt==2.0.0 +wrapt==2.0.1 # via smart-open zipp==3.23.0 # via importlib-metadata diff --git a/requirements/ubuntu-latest_py3.11_extras.txt b/requirements/ubuntu-latest_py3.11_extras.txt index aae6db26..301402c6 100644 --- a/requirements/ubuntu-latest_py3.11_extras.txt +++ b/requirements/ubuntu-latest_py3.11_extras.txt @@ -8,17 +8,17 @@ aiofiles==22.1.0 # via ypy-websocket aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via dephell aiosignal==1.4.0 # via aiohttp -aiosqlite==0.21.0 +aiosqlite==0.22.1 # via ypy-websocket alabaster==1.0.0 # via sphinx annotated-types==0.7.0 # via pydantic -anyio==4.11.0 +anyio==4.12.1 # via jupyter-server argon2-cffi==25.1.0 # via @@ -28,12 +28,12 @@ argon2-cffi-bindings==25.1.0 # via argon2-cffi arrow==1.4.0 # via isoduration -ase==3.26.0 +ase==3.27.0 # via # boltztrap2 # dscribe # hiphive -asttokens==3.0.0 +asttokens==3.0.1 # via stack-data attrs==25.4.0 # via @@ -53,14 +53,14 @@ babel==2.17.0 # via # jupyterlab-server # sphinx -beautifulsoup4==4.14.2 +beautifulsoup4==4.14.3 # via # dash # gdown # nbconvert bibtexparser==1.4.3 # via pymatgen -black==25.9.0 +black==26.1.0 # via crystal_toolkit (pyproject.toml) blake3==1.0.8 # via emmet-core @@ -68,19 +68,19 @@ bleach[css]==6.3.0 # via nbconvert blinker==1.9.0 # via flask -boltztrap2==25.3.1 +boltztrap2==25.11.1 # via ifermi -boto3==1.40.66 +boto3==1.42.33 # via mp-api -botocore==1.40.66 +botocore==1.42.33 # via # boto3 # s3transfer cachelib==0.13.0 # via flask-caching -cerberus==1.3.7 +cerberus==1.3.8 # via dephell -certifi==2025.10.5 +certifi==2026.1.4 # via # dephell # netcdf4 @@ -89,15 +89,15 @@ cffi==2.0.0 # via # argon2-cffi-bindings # cryptography -cfgv==3.4.0 +cfgv==3.5.0 # via pre-commit cftime==1.6.5 # via netcdf4 charset-normalizer==3.4.4 # via requests -choreographer==1.2.0 +choreographer==1.2.1 # via kaleido -click==8.3.0 +click==8.3.1 # via # black # dask @@ -116,7 +116,7 @@ commonmark==0.9.1 # via recommonmark contourpy==1.3.3 # via matplotlib -coverage[toml]==7.11.0 +coverage[toml]==7.13.1 # via pytest-cov cryptography==46.0.3 # via dash @@ -124,9 +124,9 @@ crystaltoolkit-extension==0.6.0 # via crystal_toolkit (pyproject.toml) cycler==0.12.1 # via matplotlib -cython==3.1.6 +cython==3.2.4 # via boltztrap2 -dash[testing]==3.2.0 +dash[testing]==3.4.0 # via # crystal_toolkit (pyproject.toml) # dash-extensions @@ -139,13 +139,13 @@ dash-testing-stub==0.0.2 # via dash dash-vtk==0.0.9 # via crystal_toolkit (pyproject.toml) -dask==2025.10.0 +dask==2026.1.1 # via # distributed # py4dstem -dataclass-wizard==0.35.1 +dataclass-wizard==0.39.1 # via dash-extensions -debugpy==1.8.17 +debugpy==1.8.19 # via ipykernel decorator==5.2.1 # via ipython @@ -184,17 +184,17 @@ dephell-venvs==0.1.18 # via dephell dephell-versioning==0.1.2 # via dephell -dill==0.4.0 +dill==0.4.1 # via # multiprocess # py4dstem distlib==0.4.0 # via virtualenv -distributed==2025.10.0 +distributed==2026.1.1 # via py4dstem dnspython==2.8.0 # via pymongo -docutils==0.21.2 +docutils==0.22.4 # via # m2r # recommonmark @@ -206,7 +206,7 @@ editorconfig==0.17.1 # via jsbeautifier emdfile==0.0.16 # via py4dstem -emmet-core==0.86.0rc1 +emmet-core==0.86.2 # via mp-api entrypoints==0.4 # via jupyter-client @@ -214,7 +214,7 @@ executing==2.2.1 # via stack-data fastjsonschema==2.21.2 # via nbformat -filelock==3.20.0 +filelock==3.20.3 # via # gdown # virtualenv @@ -226,23 +226,23 @@ flask-caching==2.3.1 # via # crystal_toolkit (pyproject.toml) # dash-extensions -fonttools==4.60.1 +fonttools==4.61.1 # via matplotlib fqdn==1.5.1 # via jsonschema -frozendict==2.4.6 +frozendict==2.4.7 # via crystal_toolkit (pyproject.toml) frozenlist==1.8.0 # via # aiohttp # aiosignal -fsspec==2025.10.0 +fsspec==2026.1.0 # via dask -gdown==5.2.0 +gdown==5.2.1 # via py4dstem gevent==25.9.1 # via gunicorn -greenlet==3.2.4 +greenlet==3.3.0 # via # gevent # playwright @@ -262,7 +262,7 @@ hdf5plugin==6.0.0 # via py4dstem hiphive==1.5 # via crystal_toolkit (pyproject.toml) -identify==2.6.15 +identify==2.6.16 # via pre-commit idna==3.11 # via @@ -276,7 +276,7 @@ imageio==2.37.2 # via scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 # via # dash # dask @@ -288,8 +288,9 @@ ipykernel==6.29.5 # via # nbclassic # notebook -ipython==9.6.0 +ipython==9.9.0 # via + # crystal_toolkit (pyproject.toml) # ipykernel # jupyterlab ipython-genutils==0.2.0 @@ -316,11 +317,11 @@ jinja2==3.1.6 # nbconvert # notebook # sphinx -jmespath==1.0.1 +jmespath==1.1.0 # via # boto3 # botocore -joblib==1.5.2 +joblib==1.5.3 # via # dscribe # pymatgen @@ -328,11 +329,11 @@ joblib==1.5.2 # scikit-optimize jsbeautifier==1.15.4 # via dash-extensions -json5==0.12.1 +json5==0.13.0 # via jupyterlab-server jsonpointer==3.0.0 # via jsonschema -jsonschema[format-nongpl]==4.25.1 +jsonschema[format-nongpl]==4.26.0 # via # jupyter-events # jupyterlab-server @@ -367,7 +368,7 @@ jupyter-server==2.17.0 # notebook-shim jupyter-server-fileid==0.9.3 # via jupyter-server-ydoc -jupyter-server-terminals==0.5.3 +jupyter-server-terminals==0.5.4 # via jupyter-server jupyter-server-ydoc==0.8.0 # via jupyterlab @@ -391,7 +392,7 @@ latexcodec==3.0.1 # via pybtex lazy-loader==0.4 # via scikit-image -llvmlite==0.45.1 +llvmlite==0.46.0 # via numba locket==1.0.0 # via @@ -411,9 +412,9 @@ markupsafe==3.0.3 # jinja2 # nbconvert # werkzeug -matminer==0.9.3 +matminer==0.10.0 # via robocrys -matplotlib==3.10.7 +matplotlib==3.10.8 # via # ase # boltztrap2 @@ -429,7 +430,7 @@ matplotlib-inline==0.2.1 # ipython meshcut==0.3.0 # via ifermi -mistune==3.1.4 +mistune==3.2.0 # via # m2r # nbconvert @@ -445,7 +446,7 @@ more-itertools==10.8.0 # via # dash-extensions # inflect -mp-api==0.45.12 +mp-api==0.45.15 # via crystal_toolkit (pyproject.toml) mpire==2.10.2 # via py4dstem @@ -459,17 +460,17 @@ multidict==6.7.0 # via # aiohttp # yarl -multiprocess==0.70.18 +multiprocess==0.70.19 # via dash mypy-extensions==1.1.0 # via black -narwhals==2.10.2 +narwhals==2.15.0 # via plotly nbclassic==1.3.3 # via # jupyterlab # notebook -nbclient==0.10.2 +nbclient==0.10.4 # via nbconvert nbconvert==7.16.6 # via @@ -490,21 +491,21 @@ nest-asyncio==1.6.0 # jupyter-client # nbclassic # notebook -netcdf4==1.7.3 +netcdf4==1.7.4 # via boltztrap2 -networkx==3.5 +networkx==3.6.1 # via # ifermi # pymatgen # robocrys # scikit-image -nodeenv==1.9.1 +nodeenv==1.10.0 # via pre-commit notebook==6.5.7 # via jupyterlab notebook-shim==0.2.4 # via nbclassic -numba==0.62.1 +numba==0.63.1 # via # hiphive # sparse @@ -550,13 +551,14 @@ numpy==1.26.4 # trimesh numpy-stl==3.2.0 # via meshcut -orjson==3.11.4 +orjson==3.11.5 # via # kaleido + # mp-api # pymatgen overrides==7.7.0 # via jupyter-server -packaging==25.0 +packaging==26.0 # via # black # dask @@ -594,7 +596,7 @@ parso==0.8.5 # via jedi partd==1.4.2 # via dask -pathspec==0.12.1 +pathspec==1.0.3 # via black percy==2.0.2 # via dash @@ -602,23 +604,23 @@ pexpect==4.9.0 # via # dephell-shells # ipython -phonopy==2.43.6 +phonopy==2.47.1 # via crystal_toolkit (pyproject.toml) -pillow==12.0.0 +pillow==12.1.0 # via # imageio # matplotlib # scikit-image -platformdirs==4.5.0 +platformdirs==4.5.1 # via # black # jupyter-core # virtualenv -playwright==1.55.0 +playwright==1.57.0 # via # crystal_toolkit (pyproject.toml) # pytest-playwright -plotly==6.4.0 +plotly==6.5.2 # via # dash # ifermi @@ -627,9 +629,9 @@ pluggy==1.6.0 # via # pytest # pytest-cov -pre-commit==4.3.0 +pre-commit==4.5.1 # via crystal_toolkit (pyproject.toml) -prometheus-client==0.23.1 +prometheus-client==0.24.1 # via # jupyter-server # notebook @@ -639,7 +641,7 @@ propcache==0.4.1 # via # aiohttp # yarl -psutil==7.1.3 +psutil==7.2.1 # via # dash # distributed @@ -661,17 +663,17 @@ pybtex==0.25.1 # via # emmet-core # robocrys -pycparser==2.23 +pycparser==3.0 # via cffi -pydantic==2.12.3 +pydantic==2.12.5 # via # dash-extensions # emmet-core # pydantic-settings # pymatgen-io-validation -pydantic-core==2.41.4 +pydantic-core==2.41.5 # via pydantic -pydantic-settings==2.11.0 +pydantic-settings==2.12.0 # via # crystal_toolkit (pyproject.toml) # emmet-core @@ -690,7 +692,7 @@ pygments==2.19.2 # nbconvert # pytest # sphinx -pylops==2.5.0 +pylops==2.6.0 # via py4dstem pymatgen==2025.10.7 # via @@ -703,15 +705,15 @@ pymatgen==2025.10.7 # robocrys pymatgen-io-validation==0.1.2 # via emmet-core -pymongo==4.15.3 +pymongo==4.16.0 # via matminer -pyparsing==3.2.5 +pyparsing==3.3.2 # via # bibtexparser # matplotlib pysocks==1.7.1 # via requests -pytest==8.4.2 +pytest==9.0.2 # via # crystal_toolkit (pyproject.toml) # dash @@ -723,7 +725,7 @@ pytest-base-url==2.1.0 # via pytest-playwright pytest-cov==7.0.0 # via crystal_toolkit (pyproject.toml) -pytest-playwright==0.7.1 +pytest-playwright==0.7.2 # via crystal_toolkit (pyproject.toml) pytest-timeout==2.4.0 # via kaleido @@ -742,7 +744,7 @@ python-slugify==8.0.4 # via pytest-playwright python-utils==3.9.1 # via numpy-stl -pytokens==0.2.0 +pytokens==0.4.0 # via black pytz==2025.2 # via pandas @@ -764,7 +766,7 @@ pyzmq==27.1.0 # notebook recommonmark==0.7.1 # via crystal_toolkit (pyproject.toml) -redis==7.0.1 +redis==7.1.0 # via crystal_toolkit (pyproject.toml) referencing==0.37.0 # via @@ -800,25 +802,22 @@ rfc3986-validator==0.1.1 # jupyter-events rfc3987-syntax==1.1.0 # via jsonschema -robocrys==0.2.11 +robocrys==0.2.13 # via crystal_toolkit (pyproject.toml) -roman-numerals-py==3.1.0 +roman-numerals==4.1.0 # via sphinx -rpds-py==0.28.0 +rpds-py==0.30.0 # via # jsonschema # referencing -ruamel-yaml==0.18.16 +ruamel-yaml==0.19.1 # via # dephell # monty # pymatgen - # robocrys -ruamel-yaml-clib==0.2.14 - # via ruamel-yaml -s3transfer==0.14.0 +s3transfer==0.16.0 # via boto3 -scikit-image==0.25.2 +scikit-image==0.26.0 # via # crystal_toolkit (pyproject.toml) # ifermi @@ -833,13 +832,14 @@ scikit-learn==1.4.2 # trainstation scikit-optimize==0.10.2 # via py4dstem -scipy==1.16.3 +scipy==1.17.0 # via # ase # boltztrap2 # dscribe # hiphive # ifermi + # matminer # meshcut # ncempy # py4dstem @@ -855,7 +855,7 @@ selenium==3.141.0 # via # crystal_toolkit (pyproject.toml) # dash -send2trash==1.8.3 +send2trash==2.1.0 # via # jupyter-server # notebook @@ -870,19 +870,17 @@ six==1.17.0 # jsbeautifier # python-dateutil # rfc3339-validator -smart-open==7.4.4 +smart-open==7.5.0 # via mp-api -sniffio==1.3.1 - # via anyio snowballstemmer==3.0.1 # via sphinx sortedcontainers==2.4.0 # via distributed -soupsieve==2.8 +soupsieve==2.8.3 # via beautifulsoup4 sparse==0.17.0 # via dscribe -spglib==2.6.0 +spglib==2.7.0 # via # boltztrap2 # hiphive @@ -891,12 +889,12 @@ spglib==2.6.0 # pymatgen # robocrys # symfc -sphinx==8.2.3 +sphinx==9.0.4 # via # recommonmark # sphinx-rtd-theme # sphinxcontrib-jquery -sphinx-rtd-theme==3.0.2 +sphinx-rtd-theme==3.1.0 # via crystal_toolkit (pyproject.toml) sphinxcontrib-applehelp==2.0.0 # via sphinx @@ -914,7 +912,7 @@ sphinxcontrib-serializinghtml==2.0.0 # via sphinx stack-data==0.6.3 # via ipython -symfc==1.5.4 +symfc==1.6.0 # via phonopy sympy==1.14.0 # via @@ -925,9 +923,9 @@ tabulate==0.9.0 # via # ifermi # pymatgen -tblib==3.2.1 +tblib==3.2.2 # via distributed -termcolor==3.2.0 +termcolor==3.3.0 # via yaspin terminado==0.18.1 # via @@ -940,18 +938,18 @@ threadpoolctl==3.6.0 # via # py4dstem # scikit-learn -tifffile==2025.10.16 +tifffile==2026.1.14 # via scikit-image tinycss2==1.4.0 # via bleach -tomlkit==0.13.3 +tomlkit==0.14.0 # via dephell toolz==1.1.0 # via # dask # distributed # partd -tornado==6.5.2 +tornado==6.5.4 # via # distributed # ipykernel @@ -984,14 +982,13 @@ traitlets==5.14.3 # nbconvert # nbformat # notebook -trimesh==4.9.0 +trimesh==4.11.1 # via ifermi typeguard==4.4.4 # via inflect typing-extensions==4.15.0 # via # aiosignal - # aiosqlite # anyio # beautifulsoup4 # blake3 @@ -1012,7 +1009,7 @@ typing-inspection==0.4.2 # via # pydantic # pydantic-settings -tzdata==2025.2 +tzdata==2025.3 # via # arrow # pandas @@ -1027,13 +1024,13 @@ urllib3==1.26.20 # distributed # requests # selenium -virtualenv==20.35.4 +virtualenv==20.36.1 # via pre-commit vtk==9.5.2 # via dash-vtk waitress==3.0.2 # via dash -wcwidth==0.2.14 +wcwidth==0.3.0 # via prompt-toolkit webcolors==25.10.0 # via @@ -1045,11 +1042,11 @@ webencodings==0.5.1 # tinycss2 websocket-client==1.9.0 # via jupyter-server -werkzeug==3.1.3 +werkzeug==3.1.5 # via # dash # flask -wrapt==2.0.0 +wrapt==2.0.1 # via smart-open y-py==0.6.2 # via @@ -1057,7 +1054,7 @@ y-py==0.6.2 # ypy-websocket yarl==1.22.0 # via aiohttp -yaspin==3.3.0 +yaspin==3.4.0 # via dephell ypy-websocket==0.8.4 # via jupyter-server-ydoc @@ -1065,9 +1062,9 @@ zict==3.0.0 # via distributed zipp==3.23.0 # via importlib-metadata -zope-event==6.0 +zope-event==6.1 # via gevent -zope-interface==8.0.1 +zope-interface==8.2 # via gevent # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/ubuntu-latest_py3.12.txt b/requirements/ubuntu-latest_py3.12.txt index 33230bd3..e33e504b 100644 --- a/requirements/ubuntu-latest_py3.12.txt +++ b/requirements/ubuntu-latest_py3.12.txt @@ -6,61 +6,73 @@ # annotated-types==0.7.0 # via pydantic +asttokens==3.0.1 + # via stack-data bibtexparser==1.4.3 # via pymatgen blake3==1.0.8 # via emmet-core blinker==1.9.0 # via flask -boto3==1.40.66 +boto3==1.42.33 # via mp-api -botocore==1.40.66 +botocore==1.42.33 # via # boto3 # s3transfer cachelib==0.13.0 # via flask-caching -certifi==2025.10.5 +certifi==2026.1.4 # via requests charset-normalizer==3.4.4 # via requests -click==8.3.0 +click==8.3.1 # via flask contourpy==1.3.3 # via matplotlib cycler==0.12.1 # via matplotlib -dash==3.2.0 +dash==3.4.0 # via crystal_toolkit (pyproject.toml) dash-mp-components==0.5.0rc0 # via crystal_toolkit (pyproject.toml) -emmet-core==0.86.0rc1 +decorator==5.2.1 + # via ipython +emmet-core==0.86.2 # via mp-api +executing==2.2.1 + # via stack-data flask==3.1.2 # via # dash # flask-caching flask-caching==2.3.1 # via crystal_toolkit (pyproject.toml) -fonttools==4.60.1 +fonttools==4.61.1 # via matplotlib -frozendict==2.4.6 +frozendict==2.4.7 # via crystal_toolkit (pyproject.toml) idna==3.11 # via requests imageio==2.37.2 # via scikit-image -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 # via dash +ipython==9.9.0 + # via crystal_toolkit (pyproject.toml) +ipython-pygments-lexers==1.1.1 + # via ipython itsdangerous==2.2.0 # via flask +jedi==0.19.2 + # via ipython jinja2==3.1.6 # via flask -jmespath==1.0.1 +jmespath==1.1.0 # via # boto3 # botocore -joblib==1.5.2 +joblib==1.5.3 # via # pymatgen # scikit-learn @@ -75,28 +87,30 @@ markupsafe==3.0.3 # flask # jinja2 # werkzeug -matplotlib==3.10.7 +matplotlib==3.10.8 # via pymatgen +matplotlib-inline==0.2.1 + # via ipython monty==2025.3.3 # via # emmet-core # mp-api # pymatgen -mp-api==0.45.12 +mp-api==0.45.15 # via crystal_toolkit (pyproject.toml) mpmath==1.3.0 # via sympy msgpack==1.1.2 # via mp-api -narwhals==2.10.2 +narwhals==2.15.0 # via plotly nest-asyncio==1.6.0 # via dash -networkx==3.5 +networkx==3.6.1 # via # pymatgen # scikit-image -numpy==2.3.4 +numpy==2.4.1 # via # contourpy # imageio @@ -111,9 +125,11 @@ numpy==2.3.4 # shapely # spglib # tifffile -orjson==3.11.4 - # via pymatgen -packaging==25.0 +orjson==3.11.5 + # via + # mp-api + # pymatgen +packaging==26.0 # via # lazy-loader # matplotlib @@ -121,31 +137,45 @@ packaging==25.0 # scikit-image palettable==3.3.3 # via pymatgen -pandas==2.3.3 +pandas==3.0.0 # via pymatgen -pillow==12.0.0 +parso==0.8.5 + # via jedi +pexpect==4.9.0 + # via ipython +pillow==12.1.0 # via # imageio # matplotlib # scikit-image -plotly==6.4.0 +plotly==6.5.2 # via # dash # pymatgen +prompt-toolkit==3.0.52 + # via ipython +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data pybtex==0.25.1 # via emmet-core -pydantic==2.12.3 +pydantic==2.12.5 # via # emmet-core # pydantic-settings # pymatgen-io-validation -pydantic-core==2.41.4 +pydantic-core==2.41.5 # via pydantic -pydantic-settings==2.11.0 +pydantic-settings==2.12.0 # via # crystal_toolkit (pyproject.toml) # emmet-core # pymatgen-io-validation +pygments==2.19.2 + # via + # ipython + # ipython-pygments-lexers pymatgen==2025.10.7 # via # crystal_toolkit (pyproject.toml) @@ -154,7 +184,7 @@ pymatgen==2025.10.7 # pymatgen-io-validation pymatgen-io-validation==0.1.2 # via emmet-core -pyparsing==3.2.5 +pyparsing==3.3.2 # via # bibtexparser # matplotlib @@ -165,8 +195,6 @@ python-dateutil==2.9.0.post0 # pandas python-dotenv==1.2.1 # via pydantic-settings -pytz==2025.2 - # via pandas pyyaml==6.0.3 # via pybtex requests==2.32.5 @@ -177,19 +205,17 @@ requests==2.32.5 # pymatgen-io-validation retrying==1.4.2 # via dash -ruamel-yaml==0.18.16 +ruamel-yaml==0.19.1 # via # monty # pymatgen -ruamel-yaml-clib==0.2.14 - # via ruamel-yaml -s3transfer==0.14.0 +s3transfer==0.16.0 # via boto3 -scikit-image==0.25.2 +scikit-image==0.26.0 # via crystal_toolkit (pyproject.toml) -scikit-learn==1.7.2 +scikit-learn==1.8.0 # via crystal_toolkit (pyproject.toml) -scipy==1.16.3 +scipy==1.17.0 # via # pymatgen # scikit-image @@ -198,20 +224,26 @@ shapely==2.1.2 # via crystal_toolkit (pyproject.toml) six==1.17.0 # via python-dateutil -smart-open==7.4.4 +smart-open==7.5.0 # via mp-api -spglib==2.6.0 +spglib==2.7.0 # via pymatgen +stack-data==0.6.3 + # via ipython sympy==1.14.0 # via pymatgen tabulate==0.9.0 # via pymatgen threadpoolctl==3.6.0 # via scikit-learn -tifffile==2025.10.16 +tifffile==2026.1.14 # via scikit-image tqdm==4.67.1 # via pymatgen +traitlets==5.14.3 + # via + # ipython + # matplotlib-inline typing-extensions==4.15.0 # via # dash @@ -225,21 +257,21 @@ typing-inspection==0.4.2 # via # pydantic # pydantic-settings -tzdata==2025.2 - # via pandas uncertainties==3.2.3 # via pymatgen -urllib3==2.5.0 +urllib3==2.6.3 # via # botocore # requests +wcwidth==0.3.0 + # via prompt-toolkit webcolors==25.10.0 # via crystal_toolkit (pyproject.toml) -werkzeug==3.1.3 +werkzeug==3.1.5 # via # dash # flask -wrapt==2.0.0 +wrapt==2.0.1 # via smart-open zipp==3.23.0 # via importlib-metadata diff --git a/requirements/ubuntu-latest_py3.12_extras.txt b/requirements/ubuntu-latest_py3.12_extras.txt index 0aa3062f..9915215f 100644 --- a/requirements/ubuntu-latest_py3.12_extras.txt +++ b/requirements/ubuntu-latest_py3.12_extras.txt @@ -8,17 +8,17 @@ aiofiles==22.1.0 # via ypy-websocket aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via dephell aiosignal==1.4.0 # via aiohttp -aiosqlite==0.21.0 +aiosqlite==0.22.1 # via ypy-websocket alabaster==1.0.0 # via sphinx annotated-types==0.7.0 # via pydantic -anyio==4.11.0 +anyio==4.12.1 # via jupyter-server argon2-cffi==25.1.0 # via @@ -28,12 +28,12 @@ argon2-cffi-bindings==25.1.0 # via argon2-cffi arrow==1.4.0 # via isoduration -ase==3.26.0 +ase==3.27.0 # via # boltztrap2 # dscribe # hiphive -asttokens==3.0.0 +asttokens==3.0.1 # via stack-data attrs==25.4.0 # via @@ -53,14 +53,14 @@ babel==2.17.0 # via # jupyterlab-server # sphinx -beautifulsoup4==4.14.2 +beautifulsoup4==4.14.3 # via # dash # gdown # nbconvert bibtexparser==1.4.3 # via pymatgen -black==25.9.0 +black==26.1.0 # via crystal_toolkit (pyproject.toml) blake3==1.0.8 # via emmet-core @@ -68,19 +68,19 @@ bleach[css]==6.3.0 # via nbconvert blinker==1.9.0 # via flask -boltztrap2==25.3.1 +boltztrap2==25.11.1 # via ifermi -boto3==1.40.66 +boto3==1.42.33 # via mp-api -botocore==1.40.66 +botocore==1.42.33 # via # boto3 # s3transfer cachelib==0.13.0 # via flask-caching -cerberus==1.3.7 +cerberus==1.3.8 # via dephell -certifi==2025.10.5 +certifi==2026.1.4 # via # dephell # netcdf4 @@ -89,15 +89,15 @@ cffi==2.0.0 # via # argon2-cffi-bindings # cryptography -cfgv==3.4.0 +cfgv==3.5.0 # via pre-commit cftime==1.6.5 # via netcdf4 charset-normalizer==3.4.4 # via requests -choreographer==1.2.0 +choreographer==1.2.1 # via kaleido -click==8.3.0 +click==8.3.1 # via # black # dask @@ -116,7 +116,7 @@ commonmark==0.9.1 # via recommonmark contourpy==1.3.3 # via matplotlib -coverage[toml]==7.11.0 +coverage[toml]==7.13.1 # via pytest-cov cryptography==46.0.3 # via dash @@ -124,9 +124,9 @@ crystaltoolkit-extension==0.6.0 # via crystal_toolkit (pyproject.toml) cycler==0.12.1 # via matplotlib -cython==3.1.6 +cython==3.2.4 # via boltztrap2 -dash[testing]==3.2.0 +dash[testing]==3.4.0 # via # crystal_toolkit (pyproject.toml) # dash-extensions @@ -139,13 +139,13 @@ dash-testing-stub==0.0.2 # via dash dash-vtk==0.0.9 # via crystal_toolkit (pyproject.toml) -dask==2025.10.0 +dask==2026.1.1 # via # distributed # py4dstem -dataclass-wizard==0.35.1 +dataclass-wizard==0.39.1 # via dash-extensions -debugpy==1.8.17 +debugpy==1.8.19 # via ipykernel decorator==5.2.1 # via ipython @@ -184,17 +184,17 @@ dephell-venvs==0.1.18 # via dephell dephell-versioning==0.1.2 # via dephell -dill==0.4.0 +dill==0.4.1 # via # multiprocess # py4dstem distlib==0.4.0 # via virtualenv -distributed==2025.10.0 +distributed==2026.1.1 # via py4dstem dnspython==2.8.0 # via pymongo -docutils==0.21.2 +docutils==0.22.4 # via # m2r # recommonmark @@ -206,7 +206,7 @@ editorconfig==0.17.1 # via jsbeautifier emdfile==0.0.16 # via py4dstem -emmet-core==0.86.0rc1 +emmet-core==0.86.2 # via mp-api entrypoints==0.4 # via jupyter-client @@ -214,7 +214,7 @@ executing==2.2.1 # via stack-data fastjsonschema==2.21.2 # via nbformat -filelock==3.20.0 +filelock==3.20.3 # via # gdown # virtualenv @@ -226,23 +226,23 @@ flask-caching==2.3.1 # via # crystal_toolkit (pyproject.toml) # dash-extensions -fonttools==4.60.1 +fonttools==4.61.1 # via matplotlib fqdn==1.5.1 # via jsonschema -frozendict==2.4.6 +frozendict==2.4.7 # via crystal_toolkit (pyproject.toml) frozenlist==1.8.0 # via # aiohttp # aiosignal -fsspec==2025.10.0 +fsspec==2026.1.0 # via dask -gdown==5.2.0 +gdown==5.2.1 # via py4dstem gevent==25.9.1 # via gunicorn -greenlet==3.2.4 +greenlet==3.3.0 # via # gevent # playwright @@ -262,7 +262,7 @@ hdf5plugin==6.0.0 # via py4dstem hiphive==1.5 # via crystal_toolkit (pyproject.toml) -identify==2.6.15 +identify==2.6.16 # via pre-commit idna==3.11 # via @@ -276,7 +276,7 @@ imageio==2.37.2 # via scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 # via dash inflect==7.5.0 # via robocrys @@ -286,8 +286,9 @@ ipykernel==6.29.5 # via # nbclassic # notebook -ipython==9.6.0 +ipython==9.9.0 # via + # crystal_toolkit (pyproject.toml) # ipykernel # jupyterlab ipython-genutils==0.2.0 @@ -314,11 +315,11 @@ jinja2==3.1.6 # nbconvert # notebook # sphinx -jmespath==1.0.1 +jmespath==1.1.0 # via # boto3 # botocore -joblib==1.5.2 +joblib==1.5.3 # via # dscribe # pymatgen @@ -326,11 +327,11 @@ joblib==1.5.2 # scikit-optimize jsbeautifier==1.15.4 # via dash-extensions -json5==0.12.1 +json5==0.13.0 # via jupyterlab-server jsonpointer==3.0.0 # via jsonschema -jsonschema[format-nongpl]==4.25.1 +jsonschema[format-nongpl]==4.26.0 # via # jupyter-events # jupyterlab-server @@ -365,7 +366,7 @@ jupyter-server==2.17.0 # notebook-shim jupyter-server-fileid==0.9.3 # via jupyter-server-ydoc -jupyter-server-terminals==0.5.3 +jupyter-server-terminals==0.5.4 # via jupyter-server jupyter-server-ydoc==0.8.0 # via jupyterlab @@ -389,7 +390,7 @@ latexcodec==3.0.1 # via pybtex lazy-loader==0.4 # via scikit-image -llvmlite==0.45.1 +llvmlite==0.46.0 # via numba locket==1.0.0 # via @@ -409,9 +410,9 @@ markupsafe==3.0.3 # jinja2 # nbconvert # werkzeug -matminer==0.9.3 +matminer==0.10.0 # via robocrys -matplotlib==3.10.7 +matplotlib==3.10.8 # via # ase # boltztrap2 @@ -427,7 +428,7 @@ matplotlib-inline==0.2.1 # ipython meshcut==0.3.0 # via ifermi -mistune==3.1.4 +mistune==3.2.0 # via # m2r # nbconvert @@ -443,7 +444,7 @@ more-itertools==10.8.0 # via # dash-extensions # inflect -mp-api==0.45.12 +mp-api==0.45.15 # via crystal_toolkit (pyproject.toml) mpire==2.10.2 # via py4dstem @@ -457,17 +458,17 @@ multidict==6.7.0 # via # aiohttp # yarl -multiprocess==0.70.18 +multiprocess==0.70.19 # via dash mypy-extensions==1.1.0 # via black -narwhals==2.10.2 +narwhals==2.15.0 # via plotly nbclassic==1.3.3 # via # jupyterlab # notebook -nbclient==0.10.2 +nbclient==0.10.4 # via nbconvert nbconvert==7.16.6 # via @@ -488,21 +489,21 @@ nest-asyncio==1.6.0 # jupyter-client # nbclassic # notebook -netcdf4==1.7.3 +netcdf4==1.7.4 # via boltztrap2 -networkx==3.5 +networkx==3.6.1 # via # ifermi # pymatgen # robocrys # scikit-image -nodeenv==1.9.1 +nodeenv==1.10.0 # via pre-commit notebook==6.5.7 # via jupyterlab notebook-shim==0.2.4 # via nbclassic -numba==0.62.1 +numba==0.63.1 # via # hiphive # sparse @@ -548,11 +549,12 @@ numpy==1.26.4 # trimesh numpy-stl==3.2.0 # via meshcut -orjson==3.11.4 +orjson==3.11.5 # via # kaleido + # mp-api # pymatgen -packaging==25.0 +packaging==26.0 # via # black # dask @@ -590,7 +592,7 @@ parso==0.8.5 # via jedi partd==1.4.2 # via dask -pathspec==0.12.1 +pathspec==1.0.3 # via black percy==2.0.2 # via dash @@ -598,23 +600,23 @@ pexpect==4.9.0 # via # dephell-shells # ipython -phonopy==2.43.6 +phonopy==2.47.1 # via crystal_toolkit (pyproject.toml) -pillow==12.0.0 +pillow==12.1.0 # via # imageio # matplotlib # scikit-image -platformdirs==4.5.0 +platformdirs==4.5.1 # via # black # jupyter-core # virtualenv -playwright==1.55.0 +playwright==1.57.0 # via # crystal_toolkit (pyproject.toml) # pytest-playwright -plotly==6.4.0 +plotly==6.5.2 # via # dash # ifermi @@ -623,9 +625,9 @@ pluggy==1.6.0 # via # pytest # pytest-cov -pre-commit==4.3.0 +pre-commit==4.5.1 # via crystal_toolkit (pyproject.toml) -prometheus-client==0.23.1 +prometheus-client==0.24.1 # via # jupyter-server # notebook @@ -635,7 +637,7 @@ propcache==0.4.1 # via # aiohttp # yarl -psutil==7.1.3 +psutil==7.2.1 # via # dash # distributed @@ -657,17 +659,17 @@ pybtex==0.25.1 # via # emmet-core # robocrys -pycparser==2.23 +pycparser==3.0 # via cffi -pydantic==2.12.3 +pydantic==2.12.5 # via # dash-extensions # emmet-core # pydantic-settings # pymatgen-io-validation -pydantic-core==2.41.4 +pydantic-core==2.41.5 # via pydantic -pydantic-settings==2.11.0 +pydantic-settings==2.12.0 # via # crystal_toolkit (pyproject.toml) # emmet-core @@ -686,7 +688,7 @@ pygments==2.19.2 # nbconvert # pytest # sphinx -pylops==2.5.0 +pylops==2.6.0 # via py4dstem pymatgen==2025.10.7 # via @@ -699,15 +701,15 @@ pymatgen==2025.10.7 # robocrys pymatgen-io-validation==0.1.2 # via emmet-core -pymongo==4.15.3 +pymongo==4.16.0 # via matminer -pyparsing==3.2.5 +pyparsing==3.3.2 # via # bibtexparser # matplotlib pysocks==1.7.1 # via requests -pytest==8.4.2 +pytest==9.0.2 # via # crystal_toolkit (pyproject.toml) # dash @@ -719,7 +721,7 @@ pytest-base-url==2.1.0 # via pytest-playwright pytest-cov==7.0.0 # via crystal_toolkit (pyproject.toml) -pytest-playwright==0.7.1 +pytest-playwright==0.7.2 # via crystal_toolkit (pyproject.toml) pytest-timeout==2.4.0 # via kaleido @@ -738,7 +740,7 @@ python-slugify==8.0.4 # via pytest-playwright python-utils==3.9.1 # via numpy-stl -pytokens==0.2.0 +pytokens==0.4.0 # via black pytz==2025.2 # via pandas @@ -760,7 +762,7 @@ pyzmq==27.1.0 # notebook recommonmark==0.7.1 # via crystal_toolkit (pyproject.toml) -redis==7.0.1 +redis==7.1.0 # via crystal_toolkit (pyproject.toml) referencing==0.37.0 # via @@ -796,25 +798,22 @@ rfc3986-validator==0.1.1 # jupyter-events rfc3987-syntax==1.1.0 # via jsonschema -robocrys==0.2.11 +robocrys==0.2.13 # via crystal_toolkit (pyproject.toml) -roman-numerals-py==3.1.0 +roman-numerals==4.1.0 # via sphinx -rpds-py==0.28.0 +rpds-py==0.30.0 # via # jsonschema # referencing -ruamel-yaml==0.18.16 +ruamel-yaml==0.19.1 # via # dephell # monty # pymatgen - # robocrys -ruamel-yaml-clib==0.2.14 - # via ruamel-yaml -s3transfer==0.14.0 +s3transfer==0.16.0 # via boto3 -scikit-image==0.25.2 +scikit-image==0.26.0 # via # crystal_toolkit (pyproject.toml) # ifermi @@ -829,13 +828,14 @@ scikit-learn==1.4.2 # trainstation scikit-optimize==0.10.2 # via py4dstem -scipy==1.16.3 +scipy==1.17.0 # via # ase # boltztrap2 # dscribe # hiphive # ifermi + # matminer # meshcut # ncempy # py4dstem @@ -851,7 +851,7 @@ selenium==3.141.0 # via # crystal_toolkit (pyproject.toml) # dash -send2trash==1.8.3 +send2trash==2.1.0 # via # jupyter-server # notebook @@ -866,19 +866,17 @@ six==1.17.0 # jsbeautifier # python-dateutil # rfc3339-validator -smart-open==7.4.4 +smart-open==7.5.0 # via mp-api -sniffio==1.3.1 - # via anyio snowballstemmer==3.0.1 # via sphinx sortedcontainers==2.4.0 # via distributed -soupsieve==2.8 +soupsieve==2.8.3 # via beautifulsoup4 sparse==0.17.0 # via dscribe -spglib==2.6.0 +spglib==2.7.0 # via # boltztrap2 # hiphive @@ -887,12 +885,12 @@ spglib==2.6.0 # pymatgen # robocrys # symfc -sphinx==8.2.3 +sphinx==9.1.0 # via # recommonmark # sphinx-rtd-theme # sphinxcontrib-jquery -sphinx-rtd-theme==3.0.2 +sphinx-rtd-theme==3.1.0 # via crystal_toolkit (pyproject.toml) sphinxcontrib-applehelp==2.0.0 # via sphinx @@ -910,7 +908,7 @@ sphinxcontrib-serializinghtml==2.0.0 # via sphinx stack-data==0.6.3 # via ipython -symfc==1.5.4 +symfc==1.6.0 # via phonopy sympy==1.14.0 # via @@ -921,9 +919,9 @@ tabulate==0.9.0 # via # ifermi # pymatgen -tblib==3.2.1 +tblib==3.2.2 # via distributed -termcolor==3.2.0 +termcolor==3.3.0 # via yaspin terminado==0.18.1 # via @@ -936,18 +934,18 @@ threadpoolctl==3.6.0 # via # py4dstem # scikit-learn -tifffile==2025.10.16 +tifffile==2026.1.14 # via scikit-image tinycss2==1.4.0 # via bleach -tomlkit==0.13.3 +tomlkit==0.14.0 # via dephell toolz==1.1.0 # via # dask # distributed # partd -tornado==6.5.2 +tornado==6.5.4 # via # distributed # ipykernel @@ -980,14 +978,13 @@ traitlets==5.14.3 # nbconvert # nbformat # notebook -trimesh==4.9.0 +trimesh==4.11.1 # via ifermi typeguard==4.4.4 # via inflect typing-extensions==4.15.0 # via # aiosignal - # aiosqlite # anyio # beautifulsoup4 # dash @@ -1006,7 +1003,7 @@ typing-inspection==0.4.2 # via # pydantic # pydantic-settings -tzdata==2025.2 +tzdata==2025.3 # via # arrow # pandas @@ -1021,13 +1018,13 @@ urllib3==1.26.20 # distributed # requests # selenium -virtualenv==20.35.4 +virtualenv==20.36.1 # via pre-commit vtk==9.5.2 # via dash-vtk waitress==3.0.2 # via dash -wcwidth==0.2.14 +wcwidth==0.3.0 # via prompt-toolkit webcolors==25.10.0 # via @@ -1039,11 +1036,11 @@ webencodings==0.5.1 # tinycss2 websocket-client==1.9.0 # via jupyter-server -werkzeug==3.1.3 +werkzeug==3.1.5 # via # dash # flask -wrapt==2.0.0 +wrapt==2.0.1 # via smart-open y-py==0.6.2 # via @@ -1051,7 +1048,7 @@ y-py==0.6.2 # ypy-websocket yarl==1.22.0 # via aiohttp -yaspin==3.3.0 +yaspin==3.4.0 # via dephell ypy-websocket==0.8.4 # via jupyter-server-ydoc @@ -1059,9 +1056,9 @@ zict==3.0.0 # via distributed zipp==3.23.0 # via importlib-metadata -zope-event==6.0 +zope-event==6.1 # via gevent -zope-interface==8.0.1 +zope-interface==8.2 # via gevent # The following packages are considered to be unsafe in a requirements file: From 87c63c2359abce693998092defd36ad00512b5c3 Mon Sep 17 00:00:00 2001 From: Aaron Kaplan <33381112+esoteric-ephemera@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:47:29 -0800 Subject: [PATCH 08/22] pin boltztrap2 for compatibility (#504) * ensure consistent c compiler for boltztrap * try building boltztrap2 separately * pin boltztrap * bump deps * bump deps * bump deps --- pyproject.toml | 3 +- requirements/ubuntu-latest_py3.11.txt | 31 +++++++++++++++++--- requirements/ubuntu-latest_py3.11_extras.txt | 8 +++-- requirements/ubuntu-latest_py3.12.txt | 31 +++++++++++++++++--- requirements/ubuntu-latest_py3.12_extras.txt | 8 +++-- 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1be35db9..5e38d4a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,8 @@ dependencies = [ "scikit-learn", "shapely", "webcolors", - "ipython" + "ipython", + "boltztrap2<25.11.1", # compilation issue when installing via pip ] [project.optional-dependencies] diff --git a/requirements/ubuntu-latest_py3.11.txt b/requirements/ubuntu-latest_py3.11.txt index 0a15a678..7e910a95 100644 --- a/requirements/ubuntu-latest_py3.11.txt +++ b/requirements/ubuntu-latest_py3.11.txt @@ -6,6 +6,8 @@ # annotated-types==0.7.0 # via pydantic +ase==3.27.0 + # via boltztrap2 asttokens==3.0.1 # via stack-data bibtexparser==1.4.3 @@ -14,6 +16,8 @@ blake3==1.0.8 # via emmet-core blinker==1.9.0 # via flask +boltztrap2==25.3.1 + # via crystal_toolkit (pyproject.toml) boto3==1.42.33 # via mp-api botocore==1.42.33 @@ -23,7 +27,11 @@ botocore==1.42.33 cachelib==0.13.0 # via flask-caching certifi==2026.1.4 - # via requests + # via + # netcdf4 + # requests +cftime==1.6.5 + # via netcdf4 charset-normalizer==3.4.4 # via requests click==8.3.1 @@ -32,6 +40,8 @@ contourpy==1.3.3 # via matplotlib cycler==0.12.1 # via matplotlib +cython==3.2.4 + # via boltztrap2 dash==3.4.0 # via crystal_toolkit (pyproject.toml) dash-mp-components==0.5.0rc0 @@ -88,7 +98,10 @@ markupsafe==3.0.3 # jinja2 # werkzeug matplotlib==3.10.8 - # via pymatgen + # via + # ase + # boltztrap2 + # pymatgen matplotlib-inline==0.2.1 # via ipython monty==2025.3.3 @@ -106,16 +119,22 @@ narwhals==2.15.0 # via plotly nest-asyncio==1.6.0 # via dash +netcdf4==1.7.4 + # via boltztrap2 networkx==3.6.1 # via # pymatgen # scikit-image numpy==2.4.1 # via + # ase + # boltztrap2 + # cftime # contourpy # imageio # matplotlib # monty + # netcdf4 # pandas # pymatgen # pymatgen-io-validation @@ -217,6 +236,8 @@ scikit-learn==1.8.0 # via crystal_toolkit (pyproject.toml) scipy==1.17.0 # via + # ase + # boltztrap2 # pymatgen # scikit-image # scikit-learn @@ -227,7 +248,9 @@ six==1.17.0 smart-open==7.5.0 # via mp-api spglib==2.7.0 - # via pymatgen + # via + # boltztrap2 + # pymatgen stack-data==0.6.3 # via ipython sympy==1.14.0 @@ -265,7 +288,7 @@ urllib3==2.6.3 # via # botocore # requests -wcwidth==0.3.0 +wcwidth==0.3.1 # via prompt-toolkit webcolors==25.10.0 # via crystal_toolkit (pyproject.toml) diff --git a/requirements/ubuntu-latest_py3.11_extras.txt b/requirements/ubuntu-latest_py3.11_extras.txt index 301402c6..ce67b7ad 100644 --- a/requirements/ubuntu-latest_py3.11_extras.txt +++ b/requirements/ubuntu-latest_py3.11_extras.txt @@ -68,8 +68,10 @@ bleach[css]==6.3.0 # via nbconvert blinker==1.9.0 # via flask -boltztrap2==25.11.1 - # via ifermi +boltztrap2==25.3.1 + # via + # crystal_toolkit (pyproject.toml) + # ifermi boto3==1.42.33 # via mp-api botocore==1.42.33 @@ -1030,7 +1032,7 @@ vtk==9.5.2 # via dash-vtk waitress==3.0.2 # via dash -wcwidth==0.3.0 +wcwidth==0.3.1 # via prompt-toolkit webcolors==25.10.0 # via diff --git a/requirements/ubuntu-latest_py3.12.txt b/requirements/ubuntu-latest_py3.12.txt index e33e504b..9c600596 100644 --- a/requirements/ubuntu-latest_py3.12.txt +++ b/requirements/ubuntu-latest_py3.12.txt @@ -6,6 +6,8 @@ # annotated-types==0.7.0 # via pydantic +ase==3.27.0 + # via boltztrap2 asttokens==3.0.1 # via stack-data bibtexparser==1.4.3 @@ -14,6 +16,8 @@ blake3==1.0.8 # via emmet-core blinker==1.9.0 # via flask +boltztrap2==25.3.1 + # via crystal_toolkit (pyproject.toml) boto3==1.42.33 # via mp-api botocore==1.42.33 @@ -23,7 +27,11 @@ botocore==1.42.33 cachelib==0.13.0 # via flask-caching certifi==2026.1.4 - # via requests + # via + # netcdf4 + # requests +cftime==1.6.5 + # via netcdf4 charset-normalizer==3.4.4 # via requests click==8.3.1 @@ -32,6 +40,8 @@ contourpy==1.3.3 # via matplotlib cycler==0.12.1 # via matplotlib +cython==3.2.4 + # via boltztrap2 dash==3.4.0 # via crystal_toolkit (pyproject.toml) dash-mp-components==0.5.0rc0 @@ -88,7 +98,10 @@ markupsafe==3.0.3 # jinja2 # werkzeug matplotlib==3.10.8 - # via pymatgen + # via + # ase + # boltztrap2 + # pymatgen matplotlib-inline==0.2.1 # via ipython monty==2025.3.3 @@ -106,16 +119,22 @@ narwhals==2.15.0 # via plotly nest-asyncio==1.6.0 # via dash +netcdf4==1.7.4 + # via boltztrap2 networkx==3.6.1 # via # pymatgen # scikit-image numpy==2.4.1 # via + # ase + # boltztrap2 + # cftime # contourpy # imageio # matplotlib # monty + # netcdf4 # pandas # pymatgen # pymatgen-io-validation @@ -217,6 +236,8 @@ scikit-learn==1.8.0 # via crystal_toolkit (pyproject.toml) scipy==1.17.0 # via + # ase + # boltztrap2 # pymatgen # scikit-image # scikit-learn @@ -227,7 +248,9 @@ six==1.17.0 smart-open==7.5.0 # via mp-api spglib==2.7.0 - # via pymatgen + # via + # boltztrap2 + # pymatgen stack-data==0.6.3 # via ipython sympy==1.14.0 @@ -263,7 +286,7 @@ urllib3==2.6.3 # via # botocore # requests -wcwidth==0.3.0 +wcwidth==0.3.1 # via prompt-toolkit webcolors==25.10.0 # via crystal_toolkit (pyproject.toml) diff --git a/requirements/ubuntu-latest_py3.12_extras.txt b/requirements/ubuntu-latest_py3.12_extras.txt index 9915215f..bada39ab 100644 --- a/requirements/ubuntu-latest_py3.12_extras.txt +++ b/requirements/ubuntu-latest_py3.12_extras.txt @@ -68,8 +68,10 @@ bleach[css]==6.3.0 # via nbconvert blinker==1.9.0 # via flask -boltztrap2==25.11.1 - # via ifermi +boltztrap2==25.3.1 + # via + # crystal_toolkit (pyproject.toml) + # ifermi boto3==1.42.33 # via mp-api botocore==1.42.33 @@ -1024,7 +1026,7 @@ vtk==9.5.2 # via dash-vtk waitress==3.0.2 # via dash -wcwidth==0.3.0 +wcwidth==0.3.1 # via prompt-toolkit webcolors==25.10.0 # via From 230221cffac4294814795ac11df974a5f00f7504 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:44:39 +0000 Subject: [PATCH 09/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 7a3ed5f5..ea43dd54 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +import json from copy import deepcopy from typing import TYPE_CHECKING, Any @@ -38,6 +39,8 @@ MIN_MAGNITUDE = 0 + + # TODOs: # - look for additional projection methods in phonon DOS (currently only atom # projections supported) @@ -88,7 +91,7 @@ def _sub_layouts(self) -> dict[str, Component]: config={"displayModeBar": False}, responsive=True, id=self.id("ph-bsdos-graph"), - style={"height": "520px"}, + style={"height": "520px"},, ) ] ) From 85fee5d4b0f201abb2237f5590ca2e2b0272a50b Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Fri, 23 Jan 2026 18:03:34 -0800 Subject: [PATCH 10/22] update required parameter to react components --- crystal_toolkit/components/phonon.py | 139 +++++++++++++++++++++++++-- crystal_toolkit/renderables/site.py | 12 ++- 2 files changed, 140 insertions(+), 11 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index ea43dd54..dc6a1bff 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -27,6 +27,8 @@ from crystal_toolkit.helpers.layouts import Column, Columns, Label, get_data_list from crystal_toolkit.helpers.pretty_labels import pretty_labels +from emmet.core.phonon import PhononBS + if TYPE_CHECKING: from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine from pymatgen.electronic_structure.dos import CompleteDos @@ -69,13 +71,14 @@ def __init__( }, **kwargs, ) - + """ bs, _ = PhononBandstructureAndDosComponent._get_ph_bs_dos( self.initial_data["default"] ) self.create_store("bs-store", bs) self.create_store("bs", None) self.create_store("dos", None) + """ @property def _sub_layouts(self) -> dict[str, Component]: @@ -439,6 +442,105 @@ def calc_animation_step(max_displacement: list, coef: int) -> list: return rdata + @staticmethod + def _complex_vectors_serialization(vectors): + # `ph_bs.eigendisplacements[band][qpoint]` is np.complex which is not serializable + # this function transfer complex eigenvector to a list of Re and Im + # For example, + # vectors = [(np.complex128(3.0634449212096337e-09+0j), + # np.complex128(-3.720119057521199e-08+0j), + # np.complex128(-0.0016537315137792753+0j)), + # (np.complex128(3.063444921240483e-09+0j), + # np.complex128(-3.720119057492181e-08+0j), + # np.complex128(-0.0016537315137792735+0j))] + # output: + # [[[3.0634449212096337e-09, 0.0], + # [-3.720119057521199e-08, 0.0], + # [-0.0016537315137792753, 0.0]], + # [[3.063444921240483e-09, 0.0], + # [-3.720119057492181e-08, 0.0], + # [-0.0016537315137792735, 0.0]]] + arr = np.asarray(vectors, dtype=np.complex128) + return np.stack([arr.real, arr.imag], axis=-1).astype(float).tolist() + + + @staticmethod + def _get_time_function_json( + ph_bs: BandStructureSymmLine, + json_data: dict, + band: int = 0, + qpoint: int = 0, + precision: int = 15, + magnitude: int = MAX_MAGNITUDE / 2, + total_repeat_cell_cnt: int = 1, + ) -> dict: + if not ph_bs or not json_data: + return {} + + assert json_data["contents"][0]["name"] == "atoms" + assert json_data["contents"][1]["name"] == "bonds" + rdata = deepcopy(json_data) + + # atoms + contents0 = json_data["contents"][0]["contents"] + for cidx, content in enumerate(contents0): + rcontent = rdata["contents"][0]["contents"][cidx] + # put required data to the given atom index + rcontent["animate"] = [] # we just need `animate` field indicating animtaion rendering + + # bonds + contents1 = json_data["contents"][1]["contents"] + for cidx, content in enumerate(contents1): + assert len(content["_meta"]) == len(content["positionPairs"]) + rcontent = rdata["contents"][0]["contents"][cidx] + rcontent["animate"] = [] + + # remove unused sense + for i in range(2, 4): + rdata["contents"][i]["visible"] = False + + # displacement formula: u(R,t) = A * e^(i(q⋅R−ωt)) + rdata["app"] = "phonon" + + # omega (ω) + rdata["omega"] = ph_bs.frequencies[band][qpoint] + + # Take mp-149 as an example: + # ph_bs.qpoints is "frac_coords of the given lattice by default (from Pymatgen)" + # transfer from frac_coords to cart_coords + # the size of ph_bs.structure.lattice.matrix: (3, 3) (lattice size) + # the size of ph_bs.qpoints: (149, 3) (wave vector for each qpoint) + # the size of q: (149, 3) + # q: + q = np.einsum( + "ij,kj->ik", + ph_bs.structure.lattice.matrix, + np.array(ph_bs.qpoints) + ).T + # phases (q⋅R): should be a number + # we calculate the phase with all atoms and qpoints here + # the size of q: (149, 3) + # the size of ph_bs.structure.cart_coords: (2, 3) (the coordinate of two atoms in the unit cell) + # the size of phase: (149, 2) + phase = np.einsum( + "ij,kj->ik", + q, + ph_bs.structure.cart_coords, + ) + rdata["phases"] = phase[qpoint].tolist() + + print(phase[qpoint]) + # amplitude (A) + rdata["amplitude"] = magnitude + + # eigenVectors + rdata["eigenVectors"] = PhononBandstructureAndDosComponent._complex_vectors_serialization(ph_bs.eigendisplacements[band][qpoint]) + + return rdata + + + + @staticmethod def _get_ph_bs_dos( data: dict[str, Any] | None, @@ -849,7 +951,7 @@ def get_figure( return figure - def generate_callbacks(self, app, cache) -> None: + def generate_callbacks(self, app, cache) -> None: @app.callback( Output(self.id("ph-bsdos-graph"), "figure"), Output(self.id("zone"), "data"), @@ -859,8 +961,12 @@ def generate_callbacks(self, app, cache) -> None: Input(self.id("ph-bsdos-graph"), "clickData"), ) def update_graph(bs, dos, nclick): + print(type(bs)) if isinstance(bs, dict): + # bs = PhononBS.from_pmg(bs) bs = PhononBandStructureSymmLine.from_dict(bs) + print(type(bs)) + print(bs.__dict__.keys()) if isinstance(dos, dict): dos = CompletePhononDos.from_dict(dos) @@ -946,7 +1052,8 @@ def update_crystal_animation( scale_z = kwargs.get("scale-z") if isinstance(bs, dict): - bs = PhononBandStructureSymmLine.from_dict(bs) + bs = PhononBS.from_pmg(bs) + # bs = PhononBandStructureSymmLine.from_dict(bs) struct = bs.structure total_repeat_cell_cnt = 1 @@ -959,14 +1066,22 @@ def update_crystal_animation( ((scale_x, 0, 0), (0, scale_y, 0), (0, 0, scale_z)) ) struct = trans.apply_transformation(struct) - struc_graph = StructureGraph.from_local_env_strategy(struct, CrystalNN()) scene = struc_graph.get_scene( draw_image_atoms=False, bonded_sites_outside_unit_cell=False, - site_get_scene_kwargs={"retain_atom_idx": True}, + site_get_scene_kwargs={ + "retain_atom_idx": True, + "total_repeat_cell_cnt": total_repeat_cell_cnt + }, ) json_data = scene.to_json() + """ + print(f"total_repeat_cell_cnt = {total_repeat_cell_cnt}") + with open("/Users/minhsuehchiu/Downloads/scene2659_super.json", "w") as f: + print("json generated") + json.dump(json_data, f) + """ qpoint = 0 band_num = 0 @@ -980,7 +1095,19 @@ def update_crystal_animation( MAX_MAGNITUDE - MIN_MAGNITUDE ) * magnitude_fraction + MIN_MAGNITUDE - return PhononBandstructureAndDosComponent._get_eigendisplacement( + output_json = PhononBandstructureAndDosComponent._get_time_function_json( + ph_bs=bs, + json_data=json_data, + band=band_num, + qpoint=qpoint, + total_repeat_cell_cnt=total_repeat_cell_cnt, + magnitude=magnitude, + ) + with open("/Users/minhsuehchiu/Downloads/scene149_time.json", "w") as f: + print("json generated") + json.dump(output_json, f) + + return PhononBandstructureAndDosComponent._get_time_function_json( ph_bs=bs, json_data=json_data, band=band_num, diff --git a/crystal_toolkit/renderables/site.py b/crystal_toolkit/renderables/site.py index bab81bfd..6f817fe1 100644 --- a/crystal_toolkit/renderables/site.py +++ b/crystal_toolkit/renderables/site.py @@ -48,6 +48,7 @@ def get_site_scene( magmom_scale: float = 1.0, legend: Legend | None = None, retain_atom_idx: bool = False, + total_repeat_cell_cnt: int = 1, ) -> Scene: """Get a Scene object for a Site. @@ -72,6 +73,7 @@ def get_site_scene( magmom_scale (float, optional): Defaults to 1.0. legend (Legend | None, optional): Defaults to None. retain_atom_idx (bool, optional): Defaults to False. + total_repeat_cell_cnt (int, optional): Defaults to 1. Returns: Scene: The scene object containing atoms, bonds, polyhedra, magmoms. @@ -137,7 +139,7 @@ def get_site_scene( phiEnd=phiEnd, clickable=True, tooltip=name, - _meta=[site_idx] if retain_atom_idx else None, + _meta=[site_idx // total_repeat_cell_cnt] if retain_atom_idx else None, ) atoms.append(sphere) @@ -210,7 +212,7 @@ def get_site_scene( radius=bond_radius / 2, clickable=True, tooltip=name_cyl, - _meta=[site_idx, connected_site.index] + _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, ) @@ -224,7 +226,7 @@ def get_site_scene( radius=bond_radius, clickable=True, tooltip=name_cyl, - _meta=[site_idx, connected_site.index] + _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, ) @@ -237,7 +239,7 @@ def get_site_scene( radius=bond_radius, clickable=True, tooltip=name_cyl, - _meta=[site_idx, connected_site.index] if retain_atom_idx else None, + _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) @@ -261,7 +263,7 @@ def get_site_scene( positionPairs=[[position, bond_midpoint.tolist()]], color=color, radius=bond_radius, - _meta=[site_idx, connected_site.index] if retain_atom_idx else None, + _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) From 84b6253f0400806399c3c11abc3bab4e03e2af84 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 02:13:50 +0000 Subject: [PATCH 11/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 6 +++--- crystal_toolkit/renderables/site.py | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index dc6a1bff..eaf213c7 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -446,7 +446,7 @@ def calc_animation_step(max_displacement: list, coef: int) -> list: def _complex_vectors_serialization(vectors): # `ph_bs.eigendisplacements[band][qpoint]` is np.complex which is not serializable # this function transfer complex eigenvector to a list of Re and Im - # For example, + # For example, # vectors = [(np.complex128(3.0634449212096337e-09+0j), # np.complex128(-3.720119057521199e-08+0j), # np.complex128(-0.0016537315137792753+0j)), @@ -498,7 +498,7 @@ def _get_time_function_json( # remove unused sense for i in range(2, 4): rdata["contents"][i]["visible"] = False - + # displacement formula: u(R,t) = A * e^(i(q⋅R−ωt)) rdata["app"] = "phonon" @@ -951,7 +951,7 @@ def get_figure( return figure - def generate_callbacks(self, app, cache) -> None: + def generate_callbacks(self, app, cache) -> None: @app.callback( Output(self.id("ph-bsdos-graph"), "figure"), Output(self.id("zone"), "data"), diff --git a/crystal_toolkit/renderables/site.py b/crystal_toolkit/renderables/site.py index 6f817fe1..8cf421e4 100644 --- a/crystal_toolkit/renderables/site.py +++ b/crystal_toolkit/renderables/site.py @@ -212,7 +212,10 @@ def get_site_scene( radius=bond_radius / 2, clickable=True, tooltip=name_cyl, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] + _meta=[ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ] if retain_atom_idx else None, ) @@ -226,7 +229,10 @@ def get_site_scene( radius=bond_radius, clickable=True, tooltip=name_cyl, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] + _meta=[ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ] if retain_atom_idx else None, ) @@ -239,7 +245,12 @@ def get_site_scene( radius=bond_radius, clickable=True, tooltip=name_cyl, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, + _meta=[ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ] + if retain_atom_idx + else None, ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) @@ -263,7 +274,12 @@ def get_site_scene( positionPairs=[[position, bond_midpoint.tolist()]], color=color, radius=bond_radius, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, + _meta=[ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ] + if retain_atom_idx + else None, ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) From 0ff6b6ec99880c9cc98cc626a4b87b7efb61d435 Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Sat, 24 Jan 2026 22:38:51 -0800 Subject: [PATCH 12/22] update data schema for React component --- crystal_toolkit/components/phonon.py | 16 +++++++--------- crystal_toolkit/core/scene.py | 2 ++ crystal_toolkit/renderables/site.py | 26 +++++++++++++++++--------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index dc6a1bff..cfc68cd0 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -94,7 +94,7 @@ def _sub_layouts(self) -> dict[str, Component]: config={"displayModeBar": False}, responsive=True, id=self.id("ph-bsdos-graph"), - style={"height": "520px"},, + style={"height": "520px"}, ) ] ) @@ -474,6 +474,7 @@ def _get_time_function_json( magnitude: int = MAX_MAGNITUDE / 2, total_repeat_cell_cnt: int = 1, ) -> dict: + """""" if not ph_bs or not json_data: return {} @@ -492,12 +493,13 @@ def _get_time_function_json( contents1 = json_data["contents"][1]["contents"] for cidx, content in enumerate(contents1): assert len(content["_meta"]) == len(content["positionPairs"]) - rcontent = rdata["contents"][0]["contents"][cidx] + rcontent = rdata["contents"][1]["contents"][cidx] rcontent["animate"] = [] - # remove unused sense - for i in range(2, 4): - rdata["contents"][i]["visible"] = False + # remove unused sense (polyhedra and magmoms) + # for i in range(2, 4): + # rdata["contents"][i]["visible"] = False + del rdata["contents"][2:4] # displacement formula: u(R,t) = A * e^(i(q⋅R−ωt)) rdata["app"] = "phonon" @@ -529,7 +531,6 @@ def _get_time_function_json( ) rdata["phases"] = phase[qpoint].tolist() - print(phase[qpoint]) # amplitude (A) rdata["amplitude"] = magnitude @@ -961,12 +962,9 @@ def generate_callbacks(self, app, cache) -> None: Input(self.id("ph-bsdos-graph"), "clickData"), ) def update_graph(bs, dos, nclick): - print(type(bs)) if isinstance(bs, dict): # bs = PhononBS.from_pmg(bs) bs = PhononBandStructureSymmLine.from_dict(bs) - print(type(bs)) - print(bs.__dict__.keys()) if isinstance(dos, dict): dos = CompletePhononDos.from_dict(dos) diff --git a/crystal_toolkit/core/scene.py b/crystal_toolkit/core/scene.py index ad2d0621..fd32bfd7 100644 --- a/crystal_toolkit/core/scene.py +++ b/crystal_toolkit/core/scene.py @@ -98,6 +98,8 @@ def remove_defaults(scene_dict): """Reduce file size of JSON by removing any key which is just its default value.""" trimmed_dict = {} for key, val in scene_dict.items(): + if key == "_meta": + trimmed_dict[key] = val if isinstance(val, dict): val = remove_defaults(val) # noqa: PLW2901 elif isinstance(val, list): diff --git a/crystal_toolkit/renderables/site.py b/crystal_toolkit/renderables/site.py index 6f817fe1..73a5186a 100644 --- a/crystal_toolkit/renderables/site.py +++ b/crystal_toolkit/renderables/site.py @@ -139,7 +139,8 @@ def get_site_scene( phiEnd=phiEnd, clickable=True, tooltip=name, - _meta=[site_idx // total_repeat_cell_cnt] if retain_atom_idx else None, + _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt], "atom_idx": [site_idx]} if retain_atom_idx else None, + # _meta=[site_idx // total_repeat_cell_cnt] if retain_atom_idx else None, ) atoms.append(sphere) @@ -212,9 +213,10 @@ def get_site_scene( radius=bond_radius / 2, clickable=True, tooltip=name_cyl, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] - if retain_atom_idx - else None, + # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] + # if retain_atom_idx + # else None, + _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, ) ) trans_vector = trans_vector + 0.25 * max_radius @@ -226,9 +228,11 @@ def get_site_scene( radius=bond_radius, clickable=True, tooltip=name_cyl, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] - if retain_atom_idx - else None, + # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] + # if retain_atom_idx + # else None, + _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, + ) bonds.append(cylinder) @@ -239,7 +243,9 @@ def get_site_scene( radius=bond_radius, clickable=True, tooltip=name_cyl, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, + # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, + _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, + ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) @@ -263,7 +269,9 @@ def get_site_scene( positionPairs=[[position, bond_midpoint.tolist()]], color=color, radius=bond_radius, - _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, + # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, + _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, + ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) From f954053ac89702ab6a69a292d36e3b6ba1617733 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 Jan 2026 06:43:41 +0000 Subject: [PATCH 13/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 27 +++++++-------- crystal_toolkit/renderables/site.py | 51 +++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index a06be8e1..e885aa8f 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -11,6 +11,7 @@ from dash.dependencies import Component, Input, Output, State from dash.exceptions import PreventUpdate from dash_mp_components import CrystalToolkitAnimationScene, CrystalToolkitScene +from emmet.core.phonon import PhononBS # crystal animation algo from pymatgen.analysis.graphs import StructureGraph @@ -27,8 +28,6 @@ from crystal_toolkit.helpers.layouts import Column, Columns, Label, get_data_list from crystal_toolkit.helpers.pretty_labels import pretty_labels -from emmet.core.phonon import PhononBS - if TYPE_CHECKING: from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine from pymatgen.electronic_structure.dos import CompleteDos @@ -41,8 +40,6 @@ MIN_MAGNITUDE = 0 - - # TODOs: # - look for additional projection methods in phonon DOS (currently only atom # projections supported) @@ -463,7 +460,6 @@ def _complex_vectors_serialization(vectors): arr = np.asarray(vectors, dtype=np.complex128) return np.stack([arr.real, arr.imag], axis=-1).astype(float).tolist() - @staticmethod def _get_time_function_json( ph_bs: BandStructureSymmLine, @@ -487,7 +483,9 @@ def _get_time_function_json( for cidx, content in enumerate(contents0): rcontent = rdata["contents"][0]["contents"][cidx] # put required data to the given atom index - rcontent["animate"] = [] # we just need `animate` field indicating animtaion rendering + rcontent[ + "animate" + ] = [] # we just need `animate` field indicating animtaion rendering # bonds contents1 = json_data["contents"][1]["contents"] @@ -500,7 +498,7 @@ def _get_time_function_json( # for i in range(2, 4): # rdata["contents"][i]["visible"] = False del rdata["contents"][2:4] - + # displacement formula: u(R,t) = A * e^(i(q⋅R−ωt)) rdata["app"] = "phonon" @@ -515,9 +513,7 @@ def _get_time_function_json( # the size of q: (149, 3) # q: q = np.einsum( - "ij,kj->ik", - ph_bs.structure.lattice.matrix, - np.array(ph_bs.qpoints) + "ij,kj->ik", ph_bs.structure.lattice.matrix, np.array(ph_bs.qpoints) ).T # phases (q⋅R): should be a number # we calculate the phase with all atoms and qpoints here @@ -535,13 +531,14 @@ def _get_time_function_json( rdata["amplitude"] = magnitude # eigenVectors - rdata["eigenVectors"] = PhononBandstructureAndDosComponent._complex_vectors_serialization(ph_bs.eigendisplacements[band][qpoint]) + rdata["eigenVectors"] = ( + PhononBandstructureAndDosComponent._complex_vectors_serialization( + ph_bs.eigendisplacements[band][qpoint] + ) + ) return rdata - - - @staticmethod def _get_ph_bs_dos( data: dict[str, Any] | None, @@ -1070,7 +1067,7 @@ def update_crystal_animation( bonded_sites_outside_unit_cell=False, site_get_scene_kwargs={ "retain_atom_idx": True, - "total_repeat_cell_cnt": total_repeat_cell_cnt + "total_repeat_cell_cnt": total_repeat_cell_cnt, }, ) json_data = scene.to_json() diff --git a/crystal_toolkit/renderables/site.py b/crystal_toolkit/renderables/site.py index 73a5186a..c2eb77ee 100644 --- a/crystal_toolkit/renderables/site.py +++ b/crystal_toolkit/renderables/site.py @@ -139,7 +139,12 @@ def get_site_scene( phiEnd=phiEnd, clickable=True, tooltip=name, - _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt], "atom_idx": [site_idx]} if retain_atom_idx else None, + _meta={ + "unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt], + "atom_idx": [site_idx], + } + if retain_atom_idx + else None, # _meta=[site_idx // total_repeat_cell_cnt] if retain_atom_idx else None, ) atoms.append(sphere) @@ -216,7 +221,16 @@ def get_site_scene( # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] # if retain_atom_idx # else None, - _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, + _meta={ + "unit_cell_atom_idx": [ + site_idx // total_repeat_cell_cnt, + connected_site.index + // total_repeat_cell_cnt, + ], + "atom_idx": [site_idx, connected_site.index], + } + if retain_atom_idx + else None, ) ) trans_vector = trans_vector + 0.25 * max_radius @@ -231,8 +245,15 @@ def get_site_scene( # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] # if retain_atom_idx # else None, - _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, - + _meta={ + "unit_cell_atom_idx": [ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ], + "atom_idx": [site_idx, connected_site.index], + } + if retain_atom_idx + else None, ) bonds.append(cylinder) @@ -244,8 +265,15 @@ def get_site_scene( clickable=True, tooltip=name_cyl, # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, - _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, - + _meta={ + "unit_cell_atom_idx": [ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ], + "atom_idx": [site_idx, connected_site.index], + } + if retain_atom_idx + else None, ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) @@ -270,8 +298,15 @@ def get_site_scene( color=color, radius=bond_radius, # _meta=[site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt] if retain_atom_idx else None, - _meta={"unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt, connected_site.index // total_repeat_cell_cnt], "atom_idx": [site_idx, connected_site.index]} if retain_atom_idx else None, - + _meta={ + "unit_cell_atom_idx": [ + site_idx // total_repeat_cell_cnt, + connected_site.index // total_repeat_cell_cnt, + ], + "atom_idx": [site_idx, connected_site.index], + } + if retain_atom_idx + else None, ) bonds.append(cylinder) all_positions.append(connected_position.tolist()) From 66e42cb585001d3c889a07eb4de1fada163878cf Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Mon, 26 Jan 2026 16:33:46 -0800 Subject: [PATCH 14/22] time based animation wip --- crystal_toolkit/components/phonon.py | 43 +++++++++++++++++++--------- crystal_toolkit/core/scene.py | 4 +-- crystal_toolkit/renderables/site.py | 4 +-- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index e885aa8f..a4247c9f 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -10,7 +10,7 @@ from dash import dcc, html from dash.dependencies import Component, Input, Output, State from dash.exceptions import PreventUpdate -from dash_mp_components import CrystalToolkitAnimationScene, CrystalToolkitScene +from dash_mp_components import CrystalToolkitAnimationScene, CrystalToolkitScene, PhononAnimationScene from emmet.core.phonon import PhononBS # crystal animation algo @@ -28,6 +28,8 @@ from crystal_toolkit.helpers.layouts import Column, Columns, Label, get_data_list from crystal_toolkit.helpers.pretty_labels import pretty_labels +from datetime import datetime + if TYPE_CHECKING: from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine from pymatgen.electronic_structure.dos import CompleteDos @@ -181,7 +183,8 @@ def _sub_layouts(self) -> dict[str, Component]: # crystal visualization crystal_animation = html.Div( - CrystalToolkitAnimationScene( + # CrystalToolkitAnimationScene( + PhononAnimationScene( data={}, sceneSize="500px", id=self.id("crystal-animation"), @@ -250,6 +253,15 @@ def _sub_layouts(self) -> dict[str, Component]: label="Vibration magnitude", ) ), + html.Div( + self.get_slider_input( + kwarg_label="velocity", + default=0.5, + step=0.01, + domain=[0, 1], + label="Velocity", + ) + ), ], style={"width": "100%"}, ) @@ -469,6 +481,7 @@ def _get_time_function_json( precision: int = 15, magnitude: int = MAX_MAGNITUDE / 2, total_repeat_cell_cnt: int = 1, + velocity: float = 1.0 ) -> dict: """""" if not ph_bs or not json_data: @@ -537,6 +550,11 @@ def _get_time_function_json( ) ) + # velocity + rdata["velocity"] = velocity + + rdata["name"] = "StructureGraphPhonon" + return rdata @staticmethod @@ -1026,14 +1044,16 @@ def highlight_bz_on_hover_bs(hover_data, click_data, label_select): State(self.get_kwarg_id("scale-x"), "value"), State(self.get_kwarg_id("scale-y"), "value"), State(self.get_kwarg_id("scale-z"), "value"), - # prevent_initial_call=True + State(self.get_kwarg_id("velocity"), "value"), + prevent_initial_call=True ) def update_crystal_animation( - cd, bs, sueprcell_update, magnitude_fraction, scale_x, scale_y, scale_z + cd, bs, sueprcell_update, magnitude_fraction, scale_x, scale_y, scale_z, velocity ): # Avoids using `get_all_kwargs_id` for all `Input`; instead, uses `State` to prevent flickering when users modify `scale_x`, `scale_y`, or `scale_z` fields, # ensuring updates occur only after the `supercell-controls-btn`` is clicked. - + print(datetime.now()) + print(bs.keys()) if not bs: raise PreventUpdate @@ -1045,6 +1065,7 @@ def update_crystal_animation( scale_x = kwargs.get("scale-x") scale_y = kwargs.get("scale-y") scale_z = kwargs.get("scale-z") + velocity = kwargs.get("velocity") if isinstance(bs, dict): bs = PhononBS.from_pmg(bs) @@ -1097,19 +1118,13 @@ def update_crystal_animation( qpoint=qpoint, total_repeat_cell_cnt=total_repeat_cell_cnt, magnitude=magnitude, + velocity=velocity ) with open("/Users/minhsuehchiu/Downloads/scene149_time.json", "w") as f: print("json generated") json.dump(output_json, f) - - return PhononBandstructureAndDosComponent._get_time_function_json( - ph_bs=bs, - json_data=json_data, - band=band_num, - qpoint=qpoint, - total_repeat_cell_cnt=total_repeat_cell_cnt, - magnitude=magnitude, - ) + print("Im here") + return output_json class PhononBandstructureAndDosPanelComponent(PanelComponent): diff --git a/crystal_toolkit/core/scene.py b/crystal_toolkit/core/scene.py index fd32bfd7..daa473c0 100644 --- a/crystal_toolkit/core/scene.py +++ b/crystal_toolkit/core/scene.py @@ -98,8 +98,8 @@ def remove_defaults(scene_dict): """Reduce file size of JSON by removing any key which is just its default value.""" trimmed_dict = {} for key, val in scene_dict.items(): - if key == "_meta": - trimmed_dict[key] = val + # if key == "_meta": + # trimmed_dict[key] = val if isinstance(val, dict): val = remove_defaults(val) # noqa: PLW2901 elif isinstance(val, list): diff --git a/crystal_toolkit/renderables/site.py b/crystal_toolkit/renderables/site.py index c2eb77ee..bb1e29d4 100644 --- a/crystal_toolkit/renderables/site.py +++ b/crystal_toolkit/renderables/site.py @@ -139,10 +139,10 @@ def get_site_scene( phiEnd=phiEnd, clickable=True, tooltip=name, - _meta={ + _meta=[{ "unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt], "atom_idx": [site_idx], - } + }] if retain_atom_idx else None, # _meta=[site_idx // total_repeat_cell_cnt] if retain_atom_idx else None, From 4b8f2ad03e03798ae5c328dbca9b9696c0de4b77 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:33:57 +0000 Subject: [PATCH 15/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 20 +++++++++++++------- crystal_toolkit/renderables/site.py | 10 ++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index a4247c9f..feeaf41c 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -3,6 +3,7 @@ import itertools import json from copy import deepcopy +from datetime import datetime from typing import TYPE_CHECKING, Any import numpy as np @@ -10,7 +11,7 @@ from dash import dcc, html from dash.dependencies import Component, Input, Output, State from dash.exceptions import PreventUpdate -from dash_mp_components import CrystalToolkitAnimationScene, CrystalToolkitScene, PhononAnimationScene +from dash_mp_components import CrystalToolkitScene, PhononAnimationScene from emmet.core.phonon import PhononBS # crystal animation algo @@ -28,8 +29,6 @@ from crystal_toolkit.helpers.layouts import Column, Columns, Label, get_data_list from crystal_toolkit.helpers.pretty_labels import pretty_labels -from datetime import datetime - if TYPE_CHECKING: from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine from pymatgen.electronic_structure.dos import CompleteDos @@ -481,7 +480,7 @@ def _get_time_function_json( precision: int = 15, magnitude: int = MAX_MAGNITUDE / 2, total_repeat_cell_cnt: int = 1, - velocity: float = 1.0 + velocity: float = 1.0, ) -> dict: """""" if not ph_bs or not json_data: @@ -1045,10 +1044,17 @@ def highlight_bz_on_hover_bs(hover_data, click_data, label_select): State(self.get_kwarg_id("scale-y"), "value"), State(self.get_kwarg_id("scale-z"), "value"), State(self.get_kwarg_id("velocity"), "value"), - prevent_initial_call=True + prevent_initial_call=True, ) def update_crystal_animation( - cd, bs, sueprcell_update, magnitude_fraction, scale_x, scale_y, scale_z, velocity + cd, + bs, + sueprcell_update, + magnitude_fraction, + scale_x, + scale_y, + scale_z, + velocity, ): # Avoids using `get_all_kwargs_id` for all `Input`; instead, uses `State` to prevent flickering when users modify `scale_x`, `scale_y`, or `scale_z` fields, # ensuring updates occur only after the `supercell-controls-btn`` is clicked. @@ -1118,7 +1124,7 @@ def update_crystal_animation( qpoint=qpoint, total_repeat_cell_cnt=total_repeat_cell_cnt, magnitude=magnitude, - velocity=velocity + velocity=velocity, ) with open("/Users/minhsuehchiu/Downloads/scene149_time.json", "w") as f: print("json generated") diff --git a/crystal_toolkit/renderables/site.py b/crystal_toolkit/renderables/site.py index bb1e29d4..ac9579e9 100644 --- a/crystal_toolkit/renderables/site.py +++ b/crystal_toolkit/renderables/site.py @@ -139,10 +139,12 @@ def get_site_scene( phiEnd=phiEnd, clickable=True, tooltip=name, - _meta=[{ - "unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt], - "atom_idx": [site_idx], - }] + _meta=[ + { + "unit_cell_atom_idx": [site_idx // total_repeat_cell_cnt], + "atom_idx": [site_idx], + } + ] if retain_atom_idx else None, # _meta=[site_idx // total_repeat_cell_cnt] if retain_atom_idx else None, From cbf258ffcb5e8e737c058fbf5e0e2b6ec1a661bf Mon Sep 17 00:00:00 2001 From: "peter810601@gmail.com" Date: Mon, 2 Feb 2026 12:45:54 -0800 Subject: [PATCH 16/22] update to latest version --- crystal_toolkit/components/phonon.py | 211 +++++---------------------- 1 file changed, 39 insertions(+), 172 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index a4247c9f..00f0c1f8 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -38,7 +38,7 @@ MARKER_COLOR = "red" MARKER_SIZE = 12 MARKER_SHAPE = "x" -MAX_MAGNITUDE = 300 +MAX_MAGNITUDE = 500 MIN_MAGNITUDE = 0 @@ -185,7 +185,7 @@ def _sub_layouts(self) -> dict[str, Component]: crystal_animation = html.Div( # CrystalToolkitAnimationScene( PhononAnimationScene( - data={}, + data={"app": "phonon"}, sceneSize="500px", id=self.id("crystal-animation"), settings={"defaultZoom": 1.2}, @@ -194,17 +194,20 @@ def _sub_layouts(self) -> dict[str, Component]: ) ) + hr = html.Hr( + style={ + "backgroundColor": "#C5C5C6", + } + ) + crystal_animation_controls = html.Div( [ - html.Br(), - html.Br(), html.Br(), html.H5("Control Panel", style={"textAlign": "center"}), - html.Br(), - html.H6("Supercell modification"), - html.Br(), + hr, + html.H6("Supercell modification", style={"textAlign": "center"}), html.Div( - [ + [ self.get_numerical_input( kwarg_label="scale-x", default=1, @@ -232,18 +235,15 @@ def _sub_layouts(self) -> dict[str, Component]: min=1, style={"width": "5rem"}, ), - html.Div( - html.Button( - "Update", - id=self.id("supercell-controls-btn"), - style={"height": "40px"}, - ), - style={"textAlign": "center", "width": "100%"}, - ), ], - style={"display": "flex"}, + style={ + "display": "flex", + "justify-content": "center", + "gap": "16px", + }, ), - html.Br(), + hr, + html.Div( self.get_slider_input( kwarg_label="magnitude", @@ -253,6 +253,7 @@ def _sub_layouts(self) -> dict[str, Component]: label="Vibration magnitude", ) ), + hr, html.Div( self.get_slider_input( kwarg_label="velocity", @@ -262,8 +263,19 @@ def _sub_layouts(self) -> dict[str, Component]: label="Velocity", ) ), + hr, + html.Div( + html.Button( + "Update", + id=self.id("supercell-controls-btn"), + style={"height": "40px"}, + ), + style={"textAlign": "center", "width": "100%"}, + ), ], - style={"width": "100%"}, + style={ + "width": "100%", + }, ) return { @@ -285,6 +297,7 @@ def _get_animation_panel(self): Column( [ sub_layouts["tip"], + html.Br(), Columns( [ sub_layouts["crystal-animation"], @@ -320,137 +333,6 @@ def layout(self) -> html.Div: return html.Div([graph, crystal_animation, controls, brillouin_zone]) - @staticmethod - def _get_eigendisplacement( - ph_bs: BandStructureSymmLine, - json_data: dict, - band: int = 0, - qpoint: int = 0, - precision: int = 15, - magnitude: int = MAX_MAGNITUDE / 2, - total_repeat_cell_cnt: int = 1, - ) -> dict: - if not ph_bs or not json_data: - return {} - - assert json_data["contents"][0]["name"] == "atoms" - assert json_data["contents"][1]["name"] == "bonds" - rdata = deepcopy(json_data) - - def calc_max_displacement(idx: int) -> list: - """ - Retrieve the eigendisplacement for a given atom index from `ph_bs` and compute its maximum displacement. - - Parameters: - idx (int): The atom index. - - Returns: - list: The maximum displacement vector in the form [x_max_displacement, y_max_displacement, z_max_displacement] - - This function extracts the real component of the atom's eigendisplacement, - scales it by the specified magnitude, and returns the resulting vector. - """ - - # get the atom index - assert total_repeat_cell_cnt != 0 - - modified_idx = ( - int(idx // total_repeat_cell_cnt) if total_repeat_cell_cnt else idx - ) - - return [ - round(complex(vec).real * magnitude, precision) - for vec in ph_bs.eigendisplacements[band][qpoint][modified_idx] - ] - - def calc_animation_step(max_displacement: list, coef: int) -> list: - """ - Calculate the displacement for an animation frame based on the given coefficient. - - Parameters: - max_displacement (list): A list of maximum displacements along each axis, - formatted as [x_max_displacement, y_max_displacement, z_max_displacement]. - coef (int): A coefficient indicating the motion direction. - - 0: no movement - - 1: forward movement - - -1: backward movement - - Returns: - list: The displacement vector [x_displacement, y_displacement, z_displacement]. - - This function generates oscillatory motion by scaling the maximum displacement - with the provided coefficient. - """ - return [round(coef * md, precision) for md in max_displacement] - - # Compute per-frame atomic motion. - # `rcontent["animate"]` stores the displacement (distance difference) from the previous coordinates. - contents0 = json_data["contents"][0]["contents"] - for cidx, content in enumerate(contents0): - max_displacement = calc_max_displacement(content["_meta"][0]) - rcontent = rdata["contents"][0]["contents"][cidx] - # put animation frame to the given atom index - rcontent["animate"] = [ - calc_animation_step(max_displacement, coef) for coef in DISPLACE_COEF - ] - rcontent["keyframes"] = list(range(len(DISPLACE_COEF))) - rcontent["animateType"] = "displacement" - # Compute per-frame bonding motion. - # Explanation: - # Each bond connects two atoms, `u` and `v`, represented as (u)----(v) - # To model the bond motion, it is divided into two segments: - # from `u` to the midpoint and from the midpoint to `v`, i.e., (u)--(mid)--(v) - # Thus, two cylinders are created: one for (u)--(mid) and another for (v)--(mid). - # For each cylinder, displacements are assigned to the endpoints — for example, - # the (u)--(mid) cylinder uses: - # [ - # [u_x_displacement, u_y_displacement, u_z_displacement], - # [mid_x_displacement, mid_y_displacement, mid_z_displacement] - # ]. - contents1 = json_data["contents"][1]["contents"] - - for cidx, content in enumerate(contents1): - bond_animation = [] - assert len(content["_meta"]) == len(content["positionPairs"]) - - for atom_idx_pair in content["_meta"]: - max_displacements = list( - map(calc_max_displacement, atom_idx_pair) - ) # max displacement for u and v - - u_to_middle_bond_animation = [] - - for coef in DISPLACE_COEF: - # Calculate the midpoint displacement between atom u and v for each animation frame. - u_displacement, v_displacement = [ - np.array(calc_animation_step(max_displacement, coef)) - for max_displacement in max_displacements - ] - middle_end_displacement = np.add(u_displacement, v_displacement) / 2 - - u_to_middle_bond_animation.append( - [ - u_displacement, # u atom displacement - [ - round(dis, precision) for dis in middle_end_displacement - ], # middle point displacement - ] - ) - - bond_animation.append(u_to_middle_bond_animation) - - rdata["contents"][1]["contents"][cidx]["animate"] = bond_animation - rdata["contents"][1]["contents"][cidx]["keyframes"] = list( - range(len(DISPLACE_COEF)) - ) - rdata["contents"][1]["contents"][cidx]["animateType"] = "displacement" - - # remove unused sense - for i in range(2, 4): - rdata["contents"][i]["visible"] = False - - return rdata - @staticmethod def _complex_vectors_serialization(vectors): # `ph_bs.eigendisplacements[band][qpoint]` is np.complex which is not serializable @@ -486,7 +368,6 @@ def _get_time_function_json( """""" if not ph_bs or not json_data: return {} - assert json_data["contents"][0]["name"] == "atoms" assert json_data["contents"][1]["name"] == "bonds" rdata = deepcopy(json_data) @@ -508,8 +389,6 @@ def _get_time_function_json( rcontent["animate"] = [] # remove unused sense (polyhedra and magmoms) - # for i in range(2, 4): - # rdata["contents"][i]["visible"] = False del rdata["contents"][2:4] # displacement formula: u(R,t) = A * e^(i(q⋅R−ωt)) @@ -526,19 +405,20 @@ def _get_time_function_json( # the size of q: (149, 3) # q: q = np.einsum( - "ij,kj->ik", ph_bs.structure.lattice.matrix, np.array(ph_bs.qpoints) + "ij,kj->ik", ph_bs.structure.lattice.reciprocal_lattice.matrix, np.array(ph_bs.qpoints) ).T + # phases (q⋅R): should be a number # we calculate the phase with all atoms and qpoints here # the size of q: (149, 3) # the size of ph_bs.structure.cart_coords: (2, 3) (the coordinate of two atoms in the unit cell) # the size of phase: (149, 2) - phase = np.einsum( + phases = np.einsum( "ij,kj->ik", q, ph_bs.structure.cart_coords, ) - rdata["phases"] = phase[qpoint].tolist() + rdata["phases"] = phases[qpoint].tolist() # amplitude (A) rdata["amplitude"] = magnitude @@ -1040,20 +920,18 @@ def highlight_bz_on_hover_bs(hover_data, click_data, label_select): Input(self.id("ph-bsdos-graph"), "clickData"), Input(self.id("ph_bs"), "data"), Input(self.id("supercell-controls-btn"), "n_clicks"), - Input(self.get_kwarg_id("magnitude"), "value"), + State(self.get_kwarg_id("magnitude"), "value"), State(self.get_kwarg_id("scale-x"), "value"), State(self.get_kwarg_id("scale-y"), "value"), State(self.get_kwarg_id("scale-z"), "value"), State(self.get_kwarg_id("velocity"), "value"), - prevent_initial_call=True + # prevent_initial_call=True ) def update_crystal_animation( cd, bs, sueprcell_update, magnitude_fraction, scale_x, scale_y, scale_z, velocity ): # Avoids using `get_all_kwargs_id` for all `Input`; instead, uses `State` to prevent flickering when users modify `scale_x`, `scale_y`, or `scale_z` fields, # ensuring updates occur only after the `supercell-controls-btn`` is clicked. - print(datetime.now()) - print(bs.keys()) if not bs: raise PreventUpdate @@ -1092,12 +970,6 @@ def update_crystal_animation( }, ) json_data = scene.to_json() - """ - print(f"total_repeat_cell_cnt = {total_repeat_cell_cnt}") - with open("/Users/minhsuehchiu/Downloads/scene2659_super.json", "w") as f: - print("json generated") - json.dump(json_data, f) - """ qpoint = 0 band_num = 0 @@ -1111,7 +983,7 @@ def update_crystal_animation( MAX_MAGNITUDE - MIN_MAGNITUDE ) * magnitude_fraction + MIN_MAGNITUDE - output_json = PhononBandstructureAndDosComponent._get_time_function_json( + return PhononBandstructureAndDosComponent._get_time_function_json( ph_bs=bs, json_data=json_data, band=band_num, @@ -1120,11 +992,6 @@ def update_crystal_animation( magnitude=magnitude, velocity=velocity ) - with open("/Users/minhsuehchiu/Downloads/scene149_time.json", "w") as f: - print("json generated") - json.dump(output_json, f) - print("Im here") - return output_json class PhononBandstructureAndDosPanelComponent(PanelComponent): From 5896edfaf74f734be6f209113b9a39a8ea906153 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:55:02 +0000 Subject: [PATCH 17/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- crystal_toolkit/components/phonon.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 52c24a79..66667483 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -1,9 +1,7 @@ from __future__ import annotations import itertools -import json from copy import deepcopy -from datetime import datetime from typing import TYPE_CHECKING, Any import numpy as np @@ -194,10 +192,10 @@ def _sub_layouts(self) -> dict[str, Component]: ) hr = html.Hr( - style={ - "backgroundColor": "#C5C5C6", - } - ) + style={ + "backgroundColor": "#C5C5C6", + } + ) crystal_animation_controls = html.Div( [ @@ -206,7 +204,7 @@ def _sub_layouts(self) -> dict[str, Component]: hr, html.H6("Supercell modification", style={"textAlign": "center"}), html.Div( - [ + [ self.get_numerical_input( kwarg_label="scale-x", default=1, @@ -242,7 +240,6 @@ def _sub_layouts(self) -> dict[str, Component]: }, ), hr, - html.Div( self.get_slider_input( kwarg_label="magnitude", @@ -404,7 +401,9 @@ def _get_time_function_json( # the size of q: (149, 3) # q: q = np.einsum( - "ij,kj->ik", ph_bs.structure.lattice.reciprocal_lattice.matrix, np.array(ph_bs.qpoints) + "ij,kj->ik", + ph_bs.structure.lattice.reciprocal_lattice.matrix, + np.array(ph_bs.qpoints), ).T # phases (q⋅R): should be a number From 4f0591f4ccd1f2c2cc59f4d3c7d459ced77528fe Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Mon, 2 Feb 2026 13:01:41 -0800 Subject: [PATCH 18/22] update with pre-commit --- crystal_toolkit/components/phonon.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 52c24a79..201b7ed0 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -1,9 +1,7 @@ from __future__ import annotations import itertools -import json from copy import deepcopy -from datetime import datetime from typing import TYPE_CHECKING, Any import numpy as np @@ -194,10 +192,10 @@ def _sub_layouts(self) -> dict[str, Component]: ) hr = html.Hr( - style={ - "backgroundColor": "#C5C5C6", - } - ) + style={ + "backgroundColor": "#C5C5C6", + } + ) crystal_animation_controls = html.Div( [ @@ -206,7 +204,7 @@ def _sub_layouts(self) -> dict[str, Component]: hr, html.H6("Supercell modification", style={"textAlign": "center"}), html.Div( - [ + [ self.get_numerical_input( kwarg_label="scale-x", default=1, @@ -242,7 +240,6 @@ def _sub_layouts(self) -> dict[str, Component]: }, ), hr, - html.Div( self.get_slider_input( kwarg_label="magnitude", @@ -373,7 +370,7 @@ def _get_time_function_json( # atoms contents0 = json_data["contents"][0]["contents"] - for cidx, content in enumerate(contents0): + for cidx, _ in enumerate(contents0): rcontent = rdata["contents"][0]["contents"][cidx] # put required data to the given atom index rcontent[ @@ -390,7 +387,7 @@ def _get_time_function_json( # remove unused sense (polyhedra and magmoms) del rdata["contents"][2:4] - # displacement formula: u(R,t) = A * e^(i(q⋅R−ωt)) + # displacement formula: u(R,t) = A * e^(i(q⋅R-ωt)) rdata["app"] = "phonon" # omega (ω) @@ -404,7 +401,9 @@ def _get_time_function_json( # the size of q: (149, 3) # q: q = np.einsum( - "ij,kj->ik", ph_bs.structure.lattice.reciprocal_lattice.matrix, np.array(ph_bs.qpoints) + "ij,kj->ik", + ph_bs.structure.lattice.reciprocal_lattice.matrix, + np.array(ph_bs.qpoints), ).T # phases (q⋅R): should be a number @@ -672,7 +671,6 @@ def get_ph_dos_traces(dos: CompletePhononDos, freq_range: tuple[float, float]): "xaxis": "x2", "yaxis": "y2", } - dos_traces.append(trace_tdos) # Projected DOS From 581f5feaf8a6849f5fcc73cbac3ed8158e6b1819 Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Tue, 3 Feb 2026 12:47:41 -0800 Subject: [PATCH 19/22] add legend, axis, and consistent color scheme --- crystal_toolkit/components/phonon.py | 80 ++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 201b7ed0..db47aa8d 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -15,12 +15,14 @@ # crystal animation algo from pymatgen.analysis.graphs import StructureGraph from pymatgen.analysis.local_env import CrystalNN +from pymatgen.core import Species from pymatgen.ext.matproj import MPRester from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine from pymatgen.phonon.dos import CompletePhononDos from pymatgen.phonon.plotter import PhononBSPlotter from pymatgen.transformations.standard_transformations import SupercellTransformation +from crystal_toolkit.core.legend import Legend from crystal_toolkit.core.mpcomponent import MPComponent from crystal_toolkit.core.panelcomponent import PanelComponent from crystal_toolkit.core.scene import Convex, Cylinders, Lines, Scene, Spheres @@ -38,6 +40,10 @@ MAX_MAGNITUDE = 500 MIN_MAGNITUDE = 0 +DEFAULTS: dict[str, str | bool] = { + "color_scheme": "VESTA", +} + # TODOs: # - look for additional projection methods in phonon DOS (currently only atom @@ -194,6 +200,8 @@ def _sub_layouts(self) -> dict[str, Component]: hr = html.Hr( style={ "backgroundColor": "#C5C5C6", + "border": "none", + "margin": "8px 0", } ) @@ -298,9 +306,14 @@ def _get_animation_panel(self): [ sub_layouts["crystal-animation"], sub_layouts["crystal-animation-controls"], - ] + ], + style={ + "display": "flex", + "justify-content": "center", + "gap": "10px", + }, ), - ] + ], ), ], ) @@ -844,6 +857,44 @@ def get_figure( return figure + def _make_legend(self, legend): + # this is copied and customized from crystal_toolkit.components.structure.StructureMoleculeComponent + # in order to get the consistent legend with the structure viewer + if not legend: + return html.Div(id=self.id("legend")) + + def get_font_color(hex_code): + # ensures contrasting font color for background color + c = tuple(int(hex_code[1:][i : i + 2], 16) for i in (0, 2, 4)) + return ( + "black" + if 1 - (c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114) / 255 < 0.5 + else "white" + ) + + legend_colors = { + key: self._legend.get_color(Species(key)) + for key, val in legend["composition"].items() + } + + legend_elements = [ + html.Span( + html.Span( + name, className="icon", style={"color": get_font_color(color)} + ), + className="button is-static is-rounded", + style={"backgroundColor": color}, + ) + for name, color in legend_colors.items() + ] + + return html.Div( + legend_elements, + id=self.id("legend"), + style={"display": "flex"}, + className="buttons", + ) + def generate_callbacks(self, app, cache) -> None: @app.callback( Output(self.id("ph-bsdos-graph"), "figure"), @@ -914,6 +965,7 @@ def highlight_bz_on_hover_bs(hover_data, click_data, label_select): @app.callback( Output(self.id("crystal-animation"), "data"), + Output(self.id("crystal-animation"), "children"), Input(self.id("ph-bsdos-graph"), "clickData"), Input(self.id("ph_bs"), "data"), Input(self.id("supercell-controls-btn"), "n_clicks"), @@ -955,6 +1007,7 @@ def update_crystal_animation( struct = bs.structure total_repeat_cell_cnt = 1 + # update structure if the controls got triggered if sueprcell_update: total_repeat_cell_cnt = scale_x * scale_y * scale_z @@ -964,7 +1017,20 @@ def update_crystal_animation( ((scale_x, 0, 0), (0, scale_y, 0), (0, 0, scale_z)) ) struct = trans.apply_transformation(struct) + struc_graph = StructureGraph.from_local_env_strategy(struct, CrystalNN()) + + # legend + legend = Legend( + struc_graph.structure, + color_scheme=DEFAULTS["color_scheme"], + # radius_scheme=radius_strategy, + cmap_range=None, + ) + self._legend = legend + legend_layout = html.Div(self._make_legend(legend.get_legend())) + + # scene scene = struc_graph.get_scene( draw_image_atoms=False, bonded_sites_outside_unit_cell=False, @@ -972,7 +1038,15 @@ def update_crystal_animation( "retain_atom_idx": True, "total_repeat_cell_cnt": total_repeat_cell_cnt, }, + legend=legend, ) + + # axis + axes = struct.lattice._axes_from_lattice() + axes.visible = True + scene.contents.append(axes) + + # json_data = scene.to_json() qpoint = 0 @@ -995,7 +1069,7 @@ def update_crystal_animation( total_repeat_cell_cnt=total_repeat_cell_cnt, magnitude=magnitude, velocity=velocity, - ) + ), [None, legend_layout] class PhononBandstructureAndDosPanelComponent(PanelComponent): From 75afde612eb8b622cfd210b5aa59aa4a0fec6ca1 Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Wed, 4 Feb 2026 16:48:01 -0800 Subject: [PATCH 20/22] add folded control panel --- crystal_toolkit/components/phonon.py | 143 ++++++++++++++------------- 1 file changed, 76 insertions(+), 67 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index db47aa8d..8843447f 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -96,7 +96,7 @@ def _sub_layouts(self) -> dict[str, Component]: config={"displayModeBar": False}, responsive=True, id=self.id("ph-bsdos-graph"), - style={"height": "520px"}, + style={"height": "400px"}, ) ] ) @@ -189,7 +189,7 @@ def _sub_layouts(self) -> dict[str, Component]: # CrystalToolkitAnimationScene( PhononAnimationScene( data={"app": "phonon"}, - sceneSize="500px", + sceneSize="400px", id=self.id("crystal-animation"), settings={"defaultZoom": 1.2}, axisView="SW", @@ -205,81 +205,90 @@ def _sub_layouts(self) -> dict[str, Component]: } ) - crystal_animation_controls = html.Div( + crystal_animation_controls = html.Details( [ - html.Br(), - html.H5("Control Panel", style={"textAlign": "center"}), - hr, - html.H6("Supercell modification", style={"textAlign": "center"}), + html.Summary("Control Panel"), html.Div( [ - self.get_numerical_input( - kwarg_label="scale-x", - default=1, - persistence_type="session", - is_int=True, - label="x", - min=1, - style={"width": "5rem"}, + # html.Br(), + # html.H5("Control Panel", style={"textAlign": "center"}), + # hr, + html.H6( + "Supercell modification", style={"textAlign": "center"} ), - self.get_numerical_input( - kwarg_label="scale-y", - default=1, - persistence_type="session", - is_int=True, - label="y", - min=1, - style={"width": "5rem"}, + html.Div( + [ + self.get_numerical_input( + kwarg_label="scale-x", + default=1, + persistence_type="session", + is_int=True, + label="x", + min=1, + style={"height": "16px"}, + ), + self.get_numerical_input( + kwarg_label="scale-y", + default=1, + persistence_type="session", + is_int=True, + label="y", + min=1, + style={"height": "16px"}, + ), + self.get_numerical_input( + kwarg_label="scale-z", + default=1, + persistence_type="session", + is_int=True, + label="z", + min=1, + style={"height": "16px"}, + ), + ], + style={ + "display": "flex", + "justify-content": "center", + "gap": "16px", + }, ), - self.get_numerical_input( - kwarg_label="scale-z", - default=1, - persistence_type="session", - is_int=True, - label="z", - min=1, - style={"width": "5rem"}, + hr, + html.Div( + self.get_slider_input( + kwarg_label="magnitude", + default=0.5, + step=0.01, + domain=[0, 1], + label="Vibration magnitude", + # styleInput={"height": "40px"}, + ), + ), + hr, + html.Div( + self.get_slider_input( + kwarg_label="velocity", + default=0.5, + step=0.01, + domain=[0, 1], + label="Velocity", + ) + ), + hr, + html.Div( + html.Button( + "Update", + id=self.id("supercell-controls-btn"), + style={"height": "40px"}, + ), + style={"textAlign": "center", "width": "100%"}, ), ], style={ - "display": "flex", - "justify-content": "center", - "gap": "16px", + "width": "100%", + # "scale": "0.9" }, ), - hr, - html.Div( - self.get_slider_input( - kwarg_label="magnitude", - default=0.5, - step=0.01, - domain=[0, 1], - label="Vibration magnitude", - ) - ), - hr, - html.Div( - self.get_slider_input( - kwarg_label="velocity", - default=0.5, - step=0.01, - domain=[0, 1], - label="Velocity", - ) - ), - hr, - html.Div( - html.Button( - "Update", - id=self.id("supercell-controls-btn"), - style={"height": "40px"}, - ), - style={"textAlign": "center", "width": "100%"}, - ), - ], - style={ - "width": "100%", - }, + ] ) return { From a191af72f397ea6a5bf917b4160e083a214263a6 Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Wed, 4 Feb 2026 17:10:03 -0800 Subject: [PATCH 21/22] bump dash-mp-components to git 0.5.0rc1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5e38d4a4..2da1dc29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ requires-python = ">=3.10" authors = [{ name = "Matt Horton", email = "mkhorton@lbl.gov" }] dependencies = [ - "dash-mp-components>=0.5.0rc0", + "dash-mp-components==0.5.0rc1", "dash>=2.11.0", "flask-caching", "frozendict", From b403bceed5708a26e98d4651a87ee62cd7531881 Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Wed, 4 Feb 2026 17:14:53 -0800 Subject: [PATCH 22/22] clean up comments --- crystal_toolkit/components/phonon.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/crystal_toolkit/components/phonon.py b/crystal_toolkit/components/phonon.py index 8843447f..0f805f54 100644 --- a/crystal_toolkit/components/phonon.py +++ b/crystal_toolkit/components/phonon.py @@ -73,14 +73,6 @@ def __init__( }, **kwargs, ) - """ - bs, _ = PhononBandstructureAndDosComponent._get_ph_bs_dos( - self.initial_data["default"] - ) - self.create_store("bs-store", bs) - self.create_store("bs", None) - self.create_store("dos", None) - """ @property def _sub_layouts(self) -> dict[str, Component]: @@ -210,9 +202,6 @@ def _sub_layouts(self) -> dict[str, Component]: html.Summary("Control Panel"), html.Div( [ - # html.Br(), - # html.H5("Control Panel", style={"textAlign": "center"}), - # hr, html.H6( "Supercell modification", style={"textAlign": "center"} ), @@ -285,7 +274,6 @@ def _sub_layouts(self) -> dict[str, Component]: ], style={ "width": "100%", - # "scale": "0.9" }, ), ] @@ -383,7 +371,6 @@ def _get_time_function_json( total_repeat_cell_cnt: int = 1, velocity: float = 1.0, ) -> dict: - """""" if not ph_bs or not json_data: return {} assert json_data["contents"][0]["name"] == "atoms"