polynet 1.0.2__tar.gz → 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {polynet-1.0.2 → polynet-1.0.3}/PKG-INFO +7 -3
- {polynet-1.0.2 → polynet-1.0.3}/README.md +4 -1
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/explain_model.py +4 -5
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/explain_tml.py +4 -5
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/representation.py +43 -65
- polynet-1.0.3/polynet/app/components/select_all.py +73 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/options/state_keys.py +2 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/descriptors.py +7 -1
- {polynet-1.0.2 → polynet-1.0.3}/polynet/pipeline/self_check.py +67 -8
- {polynet-1.0.2 → polynet-1.0.3}/polynet/visualization/plots.py +1 -1
- {polynet-1.0.2 → polynet-1.0.3}/pyproject.toml +5 -2
- {polynet-1.0.2 → polynet-1.0.3}/LICENSE +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/Welcome_to_PolyNet.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/experiments.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/figure_export.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/analyse_results.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/applicability_domain.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/create_experiment.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/explanation_plots.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/fragment_plots.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/plot_customiser.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/search_grid.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/forms/train_models.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/graph_display.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/optional_dependencies.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/plots.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/components/version_footer.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/options/allowable_sets.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/options/file_paths.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/options/plot.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/pages/1_Create_Experiment.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/pages/2_Representation.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/pages/3_Train_Models.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/pages/4_Predict.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/pages/5_Explain_Models.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/pages/6_Analyse_Results.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/cache.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/configurations.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/experiments.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/explain_model.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/explain_source.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/explain_tml.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/explanation_results.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/graph_viz.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/model_training.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/predict_model.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/progress.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/reruns.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/services/workload.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/static/polynet.png +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/app/utils/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/applicability/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/applicability/assess.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/applicability/calibration.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/applicability/domain.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/applicability/reference.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/applicability/similarity.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/cli.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/_loader.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/column_names.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/constants.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/display_names.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/enums.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/experiment.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/from_app.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/from_yaml.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/io.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/paths.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/applicability_domain.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/base.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/data.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/explainability.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/feature_preprocessing.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/fingerprints.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/general.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/plotting.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/representation.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/split_data.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/target_preprocessing.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/tml_explainability.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/schemas/training.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/config/search_grid.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/benchmarks/fluorine_mri_snr.csv +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/creator.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/feature_transformer.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/loader.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/preprocessing.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/sampling.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/data/structures.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/experiment/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/experiment/manager.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/attributions.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/embeddings.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/explain.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/fragment_figures.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/masking.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/selection.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/shap_explain.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/explainability/visualization.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/factories/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/factories/dataloader.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/factories/loss.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/factories/network.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/factories/optimizer.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/allowable_sets.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/graph.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/pmx.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/polymer_graph.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/featurizer/selection.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/inference/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/inference/ensemble.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/inference/gnn.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/inference/predict.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/inference/tml.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/inference/utils.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/base.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/cgcnn.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/cggnn.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/gat.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/gcn.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/graphsage.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/mpnn.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/gnn/transformer.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/models/persistence.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/pipeline/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/pipeline/runner.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/pipeline/stages.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/plotting/data_analysis.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/cv.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/evaluate.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/gnn.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/hyperopt.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/metrics.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/training/tml.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/chem_utils.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/data_preprocessing.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/graph_analysis.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/optional_dependencies.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/statistical_analysis.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/utils/validation.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/visualization/__init__.py +0 -0
- {polynet-1.0.2 → polynet-1.0.3}/polynet/visualization/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polynet
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: A Python package for polymer informatics: property prediction with graph neural networks and traditional machine learning
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -30,7 +30,8 @@ Requires-Dist: shap (>=0.51.0,<0.52.0)
|
|
|
30
30
|
Requires-Dist: statsmodels (>=0.14.5,<0.15.0)
|
|
31
31
|
Requires-Dist: streamlit (>=1.45.1,<2.0.0)
|
|
32
32
|
Requires-Dist: torch (==2.2.0) ; sys_platform == "darwin" and platform_machine == "x86_64"
|
|
33
|
-
Requires-Dist: torch (>=2.5.1,<
|
|
33
|
+
Requires-Dist: torch (>=2.5.1,<2.11) ; sys_platform == "darwin" and platform_machine == "arm64"
|
|
34
|
+
Requires-Dist: torch (>=2.5.1,<3.0.0) ; sys_platform != "darwin"
|
|
34
35
|
Requires-Dist: torch-geometric (==2.7)
|
|
35
36
|
Requires-Dist: xgboost (>=3.0.2,<4.0.0)
|
|
36
37
|
Project-URL: Changelog, https://github.com/Biomaterials-for-Medical-Devices-AI/PolyNet/blob/main/CHANGELOG.md
|
|
@@ -129,6 +130,9 @@ Then install PolyNet:
|
|
|
129
130
|
pip install polynet
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
On macOS, XGBoost also needs the OpenMP library from [Homebrew](https://brew.sh):
|
|
134
|
+
`brew install libomp`.
|
|
135
|
+
|
|
132
136
|
This installs the `polynet` command: `polynet` opens the GUI and `polynet run --config
|
|
133
137
|
experiment.yaml` runs the pipeline (see Quick Start below).
|
|
134
138
|
|
|
@@ -324,7 +328,7 @@ If you use PolyNet, please cite the software (GitHub's "Cite this repository" bu
|
|
|
324
328
|
@software{polynet,
|
|
325
329
|
author = {Aguilar-Bejarano, Eduardo and Alexander, Morgan and Winkler, David and Figueredo, Grazziela},
|
|
326
330
|
title = {PolyNet: A Python Package for Polymer Informatics},
|
|
327
|
-
version = {1.0.
|
|
331
|
+
version = {1.0.3},
|
|
328
332
|
year = {2026},
|
|
329
333
|
doi = {10.5281/zenodo.23302984},
|
|
330
334
|
url = {https://github.com/Biomaterials-for-Medical-Devices-AI/PolyNet}
|
|
@@ -89,6 +89,9 @@ Then install PolyNet:
|
|
|
89
89
|
pip install polynet
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
On macOS, XGBoost also needs the OpenMP library from [Homebrew](https://brew.sh):
|
|
93
|
+
`brew install libomp`.
|
|
94
|
+
|
|
92
95
|
This installs the `polynet` command: `polynet` opens the GUI and `polynet run --config
|
|
93
96
|
experiment.yaml` runs the pipeline (see Quick Start below).
|
|
94
97
|
|
|
@@ -284,7 +287,7 @@ If you use PolyNet, please cite the software (GitHub's "Cite this repository" bu
|
|
|
284
287
|
@software{polynet,
|
|
285
288
|
author = {Aguilar-Bejarano, Eduardo and Alexander, Morgan and Winkler, David and Figueredo, Grazziela},
|
|
286
289
|
title = {PolyNet: A Python Package for Polymer Informatics},
|
|
287
|
-
version = {1.0.
|
|
290
|
+
version = {1.0.3},
|
|
288
291
|
year = {2026},
|
|
289
292
|
doi = {10.5281/zenodo.23302984},
|
|
290
293
|
url = {https://github.com/Biomaterials-for-Medical-Devices-AI/PolyNet}
|
|
@@ -5,6 +5,7 @@ from rdkit.Chem import Descriptors
|
|
|
5
5
|
import streamlit as st
|
|
6
6
|
from torch_geometric.data import Dataset
|
|
7
7
|
|
|
8
|
+
from polynet.app.components.select_all import multiselect_with_select_all
|
|
8
9
|
from polynet.app.options.state_keys import ExplainModelStateKeys, ProjectionPlotStateKeys
|
|
9
10
|
from polynet.app.services.explain_model import (
|
|
10
11
|
analyse_graph_embeddings,
|
|
@@ -252,14 +253,12 @@ def _shared_params_section(
|
|
|
252
253
|
all_archs = sorted({_parse_gnn_filename(f)[0] for f in gnn_models})
|
|
253
254
|
all_iterations = sorted({_parse_gnn_filename(f)[1] for f in gnn_models})
|
|
254
255
|
|
|
255
|
-
|
|
256
|
-
"Select all GNN models", key=ExplainModelStateKeys.SelectAllModels, value=False
|
|
257
|
-
)
|
|
258
|
-
selected_archs = st.multiselect(
|
|
256
|
+
selected_archs = multiselect_with_select_all(
|
|
259
257
|
"Select GNN architectures to explain",
|
|
260
258
|
options=all_archs,
|
|
261
|
-
default=all_archs if select_all else None,
|
|
262
259
|
key=ExplainModelStateKeys.GNNArchSelector,
|
|
260
|
+
select_all_label="Select all GNN models",
|
|
261
|
+
select_all_key=ExplainModelStateKeys.SelectAllModels,
|
|
263
262
|
)
|
|
264
263
|
selected_iterations = st.multiselect(
|
|
265
264
|
"Select bootstrap iterations",
|
|
@@ -16,6 +16,7 @@ import pandas as pd
|
|
|
16
16
|
import streamlit as st
|
|
17
17
|
|
|
18
18
|
from polynet.app.components.forms.explain_model import explain_mols_widget
|
|
19
|
+
from polynet.app.components.select_all import multiselect_with_select_all
|
|
19
20
|
from polynet.app.options.state_keys import TMLExplainStateKeys
|
|
20
21
|
from polynet.app.services.explain_tml import explain_tml_global, explain_tml_local
|
|
21
22
|
from polynet.app.services.reruns import explanation_rerun_button
|
|
@@ -58,14 +59,12 @@ def _tml_shared_params_section(
|
|
|
58
59
|
)
|
|
59
60
|
all_iterations = sorted({int(key.rsplit("_", 1)[1]) for key in tml_model_log_names})
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
"Select all TML models", key=TMLExplainStateKeys.SelectAllTMLModels, value=False
|
|
63
|
-
)
|
|
64
|
-
selected_model_types = st.multiselect(
|
|
62
|
+
selected_model_types = multiselect_with_select_all(
|
|
65
63
|
"Select TML model types to explain",
|
|
66
64
|
options=all_model_types,
|
|
67
|
-
default=all_model_types if select_all else None,
|
|
68
65
|
key=TMLExplainStateKeys.TMLModelSelector,
|
|
66
|
+
select_all_label="Select all TML models",
|
|
67
|
+
select_all_key=TMLExplainStateKeys.SelectAllTMLModels,
|
|
69
68
|
)
|
|
70
69
|
|
|
71
70
|
selected_reprs = st.multiselect(
|
|
@@ -5,6 +5,7 @@ from rdkit import Chem
|
|
|
5
5
|
from rdkit.Chem import Descriptors
|
|
6
6
|
import streamlit as st
|
|
7
7
|
|
|
8
|
+
from polynet.app.components.select_all import multiselect_with_select_all
|
|
8
9
|
from polynet.app.options.allowable_sets import atom_properties, bond_features
|
|
9
10
|
from polynet.app.options.state_keys import DescriptorCalculationStateKeys
|
|
10
11
|
from polynet.app.utils import keep_only_numerical_columns
|
|
@@ -277,23 +278,16 @@ def molecular_descriptor_representation(
|
|
|
277
278
|
|
|
278
279
|
st.markdown("### Calculate RDKit Molecular Descriptors")
|
|
279
280
|
|
|
280
|
-
|
|
281
|
-
"Select all descriptors",
|
|
282
|
-
value=not is_psmiles,
|
|
283
|
-
key=DescriptorCalculationStateKeys.SelectAllRDKitDescriptors,
|
|
284
|
-
help="If checked, all RDKit molecular descriptors will be selected by default. Uncheck to select specific descriptors.",
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
if select_all:
|
|
288
|
-
default_descriptors = all_descriptors
|
|
289
|
-
else:
|
|
290
|
-
default_descriptors = defaults.get(MolecularDescriptor.RDKit, [])
|
|
291
|
-
|
|
292
|
-
selected_descriptors = st.multiselect(
|
|
281
|
+
selected_descriptors = multiselect_with_select_all(
|
|
293
282
|
"Select descriptors to calculate:",
|
|
294
283
|
options=all_descriptors,
|
|
295
|
-
default=default_descriptors, # Preselect common ones
|
|
296
284
|
key=DescriptorCalculationStateKeys.DescriptorsRDKit,
|
|
285
|
+
select_all_label="Select all descriptors",
|
|
286
|
+
select_all_key=DescriptorCalculationStateKeys.SelectAllRDKitDescriptors,
|
|
287
|
+
select_all_default=not is_psmiles,
|
|
288
|
+
partial_selection=defaults.get(MolecularDescriptor.RDKit, []),
|
|
289
|
+
use_checkbox=True,
|
|
290
|
+
select_all_help="If checked, all RDKit molecular descriptors will be selected by default. Uncheck to select specific descriptors.",
|
|
297
291
|
help="Choose which RDKit descriptors to compute for the molecules.",
|
|
298
292
|
)
|
|
299
293
|
st.info(f"You have selected {len((selected_descriptors))} RDKit descriptors.")
|
|
@@ -380,43 +374,37 @@ def molecular_descriptor_representation(
|
|
|
380
374
|
BACKBONE_TOPOLOGICAL_FEATURIZER_REGISTRY.keys()
|
|
381
375
|
)
|
|
382
376
|
|
|
383
|
-
|
|
384
|
-
"Select all repeat-unit descriptors",
|
|
385
|
-
value=True,
|
|
386
|
-
key=DescriptorCalculationStateKeys.SelectAllPMXRepeatUnit,
|
|
387
|
-
help="If checked, every repeat-unit descriptor is selected by default. Uncheck to choose specific descriptors.",
|
|
388
|
-
)
|
|
389
|
-
polymer_descriptors = st.multiselect(
|
|
377
|
+
polymer_descriptors = multiselect_with_select_all(
|
|
390
378
|
"Select the descriptors to calculate for the monomer repeat unit.",
|
|
391
379
|
options=repeat_unit_options,
|
|
392
|
-
default=repeat_unit_options if select_all_repeat_unit else None,
|
|
393
380
|
key=DescriptorCalculationStateKeys.PMXRepeatUnit,
|
|
381
|
+
select_all_label="Select all repeat-unit descriptors",
|
|
382
|
+
select_all_key=DescriptorCalculationStateKeys.SelectAllPMXRepeatUnit,
|
|
383
|
+
select_all_default=True,
|
|
384
|
+
use_checkbox=True,
|
|
385
|
+
select_all_help="If checked, every repeat-unit descriptor is selected by default. Uncheck to choose specific descriptors.",
|
|
394
386
|
)
|
|
395
387
|
|
|
396
|
-
|
|
397
|
-
"Select all side-chain descriptors",
|
|
398
|
-
value=True,
|
|
399
|
-
key=DescriptorCalculationStateKeys.SelectAllPMXSideChainDescriptors,
|
|
400
|
-
help="If checked, every side-chain descriptor is selected by default. Uncheck to choose specific descriptors.",
|
|
401
|
-
)
|
|
402
|
-
side_chain_descriptors = st.multiselect(
|
|
388
|
+
side_chain_descriptors = multiselect_with_select_all(
|
|
403
389
|
"Select the descriptors to calculate for the side chain of the polymers",
|
|
404
390
|
options=side_chain_options,
|
|
405
|
-
default=side_chain_options if select_all_side_chain else None,
|
|
406
391
|
key=DescriptorCalculationStateKeys.PMXSideChainDescriptors,
|
|
392
|
+
select_all_label="Select all side-chain descriptors",
|
|
393
|
+
select_all_key=DescriptorCalculationStateKeys.SelectAllPMXSideChainDescriptors,
|
|
394
|
+
select_all_default=True,
|
|
395
|
+
use_checkbox=True,
|
|
396
|
+
select_all_help="If checked, every side-chain descriptor is selected by default. Uncheck to choose specific descriptors.",
|
|
407
397
|
)
|
|
408
398
|
|
|
409
|
-
|
|
410
|
-
"Select all backbone descriptors",
|
|
411
|
-
value=True,
|
|
412
|
-
key=DescriptorCalculationStateKeys.SelectAllPMXBackboneDescriptors,
|
|
413
|
-
help="If checked, every backbone descriptor is selected by default. Uncheck to choose specific descriptors.",
|
|
414
|
-
)
|
|
415
|
-
backbone_descriptors = st.multiselect(
|
|
399
|
+
backbone_descriptors = multiselect_with_select_all(
|
|
416
400
|
"Select the descriptors to calculate for the backbone of the polymers",
|
|
417
401
|
options=backbone_options,
|
|
418
|
-
default=backbone_options if select_all_backbone else None,
|
|
419
402
|
key=DescriptorCalculationStateKeys.PMXBackboneDescriptors,
|
|
403
|
+
select_all_label="Select all backbone descriptors",
|
|
404
|
+
select_all_key=DescriptorCalculationStateKeys.SelectAllPMXBackboneDescriptors,
|
|
405
|
+
select_all_default=True,
|
|
406
|
+
use_checkbox=True,
|
|
407
|
+
select_all_help="If checked, every backbone descriptor is selected by default. Uncheck to choose specific descriptors.",
|
|
420
408
|
)
|
|
421
409
|
|
|
422
410
|
agg_method = st.multiselect(
|
|
@@ -558,24 +546,20 @@ def graph_representation(
|
|
|
558
546
|
"removed from the options below."
|
|
559
547
|
)
|
|
560
548
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
549
|
+
selected_atomic_properties = multiselect_with_select_all(
|
|
550
|
+
"Select atomic properties to compute:",
|
|
551
|
+
options=atomic_properties,
|
|
552
|
+
key=DescriptorCalculationStateKeys.AtomProperties,
|
|
553
|
+
select_all_label="Select all atomic properties",
|
|
554
|
+
select_all_key=DescriptorCalculationStateKeys.SelectAllAtomProperties,
|
|
555
|
+
partial_selection=[
|
|
567
556
|
AtomFeature.GetAtomicNum,
|
|
568
557
|
AtomFeature.GetTotalNumHs,
|
|
569
558
|
AtomFeature.GetTotalDegree,
|
|
570
559
|
AtomFeature.GetImplicitValence,
|
|
571
560
|
AtomFeature.GetIsAromatic,
|
|
572
|
-
]
|
|
573
|
-
|
|
574
|
-
selected_atomic_properties = st.multiselect(
|
|
575
|
-
"Select atomic properties to compute:",
|
|
576
|
-
options=atomic_properties,
|
|
577
|
-
default=default_atomic_properties,
|
|
578
|
-
key=DescriptorCalculationStateKeys.AtomProperties,
|
|
561
|
+
],
|
|
562
|
+
use_checkbox=True,
|
|
579
563
|
help="Choose which RDKit Atom-level properties to extract.",
|
|
580
564
|
)
|
|
581
565
|
|
|
@@ -673,23 +657,17 @@ def graph_representation(
|
|
|
673
657
|
|
|
674
658
|
st.markdown("#### Bond Properties")
|
|
675
659
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if select_all_bond:
|
|
679
|
-
default_bond_properties = bond_properties
|
|
680
|
-
|
|
681
|
-
else:
|
|
682
|
-
default_bond_properties = [
|
|
683
|
-
BondFeature.GetBondTypeAsDouble,
|
|
684
|
-
BondFeature.GetIsAromatic,
|
|
685
|
-
BondFeature.GetStereo,
|
|
686
|
-
]
|
|
687
|
-
|
|
688
|
-
selected_bond_properties = st.multiselect(
|
|
660
|
+
selected_bond_properties = multiselect_with_select_all(
|
|
689
661
|
"Select bond properties to compute:",
|
|
690
662
|
options=bond_properties,
|
|
691
|
-
default=default_bond_properties,
|
|
692
663
|
key=DescriptorCalculationStateKeys.BondProperties,
|
|
664
|
+
select_all_label="Select all bond properties",
|
|
665
|
+
select_all_key=DescriptorCalculationStateKeys.SelectAllBondProperties,
|
|
666
|
+
partial_selection=[
|
|
667
|
+
BondFeature.GetBondTypeAsDouble,
|
|
668
|
+
BondFeature.GetIsAromatic,
|
|
669
|
+
BondFeature.GetStereo,
|
|
670
|
+
],
|
|
693
671
|
help="Choose which RDKit Bond-level properties to extract.",
|
|
694
672
|
)
|
|
695
673
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
polynet.app.components.select_all
|
|
3
|
+
=================================
|
|
4
|
+
A multiselect with a "select all" toggle or checkbox.
|
|
5
|
+
|
|
6
|
+
Recent Streamlit versions keep a keyed widget's value when its ``default``
|
|
7
|
+
changes, so a toggle that only changes the multiselect's ``default`` does
|
|
8
|
+
nothing there. Here the toggle sets the selection itself, through the session
|
|
9
|
+
state, when it is clicked.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
14
|
+
import streamlit as st
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def multiselect_with_select_all(
|
|
18
|
+
label: str,
|
|
19
|
+
options: Sequence,
|
|
20
|
+
key: str,
|
|
21
|
+
*,
|
|
22
|
+
select_all_label: str,
|
|
23
|
+
select_all_key: str,
|
|
24
|
+
select_all_default: bool = False,
|
|
25
|
+
partial_selection: Sequence | None = None,
|
|
26
|
+
use_checkbox: bool = False,
|
|
27
|
+
select_all_help: str | None = None,
|
|
28
|
+
**multiselect_kwargs,
|
|
29
|
+
) -> list:
|
|
30
|
+
"""
|
|
31
|
+
Render a "select all" toggle (or checkbox) above a multiselect.
|
|
32
|
+
|
|
33
|
+
Switching it on selects every option; switching it off selects
|
|
34
|
+
``partial_selection`` (nothing by default). The user can still edit the
|
|
35
|
+
selection afterwards.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
label: Label of the multiselect.
|
|
39
|
+
options: The multiselect options.
|
|
40
|
+
key: Session-state key of the multiselect.
|
|
41
|
+
select_all_label: Label of the toggle.
|
|
42
|
+
select_all_key: Session-state key of the toggle.
|
|
43
|
+
select_all_default: Whether the toggle starts switched on.
|
|
44
|
+
partial_selection: Selection while the toggle is off.
|
|
45
|
+
use_checkbox: Show a checkbox instead of a toggle.
|
|
46
|
+
select_all_help: Help text of the toggle.
|
|
47
|
+
**multiselect_kwargs: Passed to ``st.multiselect`` (e.g. ``help``).
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The selected options.
|
|
51
|
+
"""
|
|
52
|
+
options = list(options)
|
|
53
|
+
partial = [o for o in (partial_selection or []) if o in options]
|
|
54
|
+
|
|
55
|
+
def selection(select_all: bool) -> list:
|
|
56
|
+
return list(options) if select_all else list(partial)
|
|
57
|
+
|
|
58
|
+
def apply_select_all() -> None:
|
|
59
|
+
st.session_state[key] = selection(st.session_state[select_all_key])
|
|
60
|
+
|
|
61
|
+
(st.checkbox if use_checkbox else st.toggle)(
|
|
62
|
+
select_all_label,
|
|
63
|
+
value=select_all_default,
|
|
64
|
+
key=select_all_key,
|
|
65
|
+
help=select_all_help,
|
|
66
|
+
on_change=apply_select_all,
|
|
67
|
+
)
|
|
68
|
+
if key not in st.session_state:
|
|
69
|
+
st.session_state[key] = selection(st.session_state[select_all_key])
|
|
70
|
+
else:
|
|
71
|
+
# Options can change between runs (e.g. another experiment).
|
|
72
|
+
st.session_state[key] = [o for o in st.session_state[key] if o in options]
|
|
73
|
+
return st.multiselect(label, options=options, key=key, **multiselect_kwargs)
|
|
@@ -58,6 +58,8 @@ class DescriptorCalculationStateKeys(StrEnum):
|
|
|
58
58
|
SelectAllPMXBackboneDescriptors = "SelectAllPMXBackboneDescriptors"
|
|
59
59
|
AtomProperties = "AtomProperties"
|
|
60
60
|
BondProperties = "BondProperties"
|
|
61
|
+
SelectAllAtomProperties = "SelectAllAtomProperties"
|
|
62
|
+
SelectAllBondProperties = "SelectAllBondProperties"
|
|
61
63
|
ShowFrequency = "ShowFrequency"
|
|
62
64
|
|
|
63
65
|
|
|
@@ -531,7 +531,13 @@ def get_polybert_fingerprints(
|
|
|
531
531
|
|
|
532
532
|
canonicalize = import_psmiles_canonicaliser()
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
import torch
|
|
535
|
+
|
|
536
|
+
# Same device rule as GNN training (CUDA or CPU). sentence-transformers would
|
|
537
|
+
# otherwise use Apple's MPS, whose embeddings differ slightly from the CPU ones
|
|
538
|
+
# (so results would depend on the machine) and are NaN on some virtual Macs.
|
|
539
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
540
|
+
model = SentenceTransformer(POLYBERT_MODEL, device=device)
|
|
535
541
|
|
|
536
542
|
# 1) Canonicalize (keep a mapping from original -> canonical)
|
|
537
543
|
orig_to_canon: dict[str, str] = {}
|
|
@@ -9,6 +9,10 @@ dataset, once for regression and once for classification, in a temporary
|
|
|
9
9
|
folder, and prints a PASS / FAIL table that can be pasted into a bug report.
|
|
10
10
|
The regression run also explains the models and predicts on new data.
|
|
11
11
|
|
|
12
|
+
XGBoost is first trained in a separate process with PyTorch loaded: on macOS
|
|
13
|
+
two OpenMP runtimes in one process crash it, which would end the check before
|
|
14
|
+
its report. The pipeline runs train XGBoost only if that passes.
|
|
15
|
+
|
|
12
16
|
Usage
|
|
13
17
|
-----
|
|
14
18
|
polynet check # about a minute on a laptop
|
|
@@ -25,6 +29,8 @@ import logging
|
|
|
25
29
|
from pathlib import Path
|
|
26
30
|
import platform
|
|
27
31
|
import shutil
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
28
34
|
import tempfile
|
|
29
35
|
import time
|
|
30
36
|
import warnings
|
|
@@ -108,11 +114,16 @@ def synthetic_copolymers(n_samples: int, problem_type: str, seed: int = 42) -> p
|
|
|
108
114
|
|
|
109
115
|
|
|
110
116
|
def check_config(
|
|
111
|
-
problem_type: str,
|
|
117
|
+
problem_type: str,
|
|
118
|
+
data_path: Path,
|
|
119
|
+
out_dir: Path,
|
|
120
|
+
epochs: int,
|
|
121
|
+
predict_path: Path | None,
|
|
122
|
+
xgboost: bool = True,
|
|
112
123
|
) -> dict:
|
|
113
124
|
"""
|
|
114
|
-
Return a small but complete pipeline config: GCN
|
|
115
|
-
Morgan fingerprints, two repeated splits.
|
|
125
|
+
Return a small but complete pipeline config: GCN, random forest and
|
|
126
|
+
XGBoost on Morgan fingerprints, two repeated splits.
|
|
116
127
|
|
|
117
128
|
Args:
|
|
118
129
|
problem_type (str): ``"regression"`` or ``"classification"``.
|
|
@@ -121,6 +132,7 @@ def check_config(
|
|
|
121
132
|
epochs (int): GNN training epochs.
|
|
122
133
|
predict_path (Path | None): CSV to predict after training; when given,
|
|
123
134
|
the models are also explained.
|
|
135
|
+
xgboost (bool): Also train XGBoost.
|
|
124
136
|
|
|
125
137
|
Returns:
|
|
126
138
|
dict: The config, as ``polynet run`` reads it from YAML.
|
|
@@ -175,7 +187,10 @@ def check_config(
|
|
|
175
187
|
"feature_preprocessing": {"scaler": "standard_scaler"},
|
|
176
188
|
"tml_models": {
|
|
177
189
|
"train_tml": True,
|
|
178
|
-
"selected_models": {
|
|
190
|
+
"selected_models": {
|
|
191
|
+
"random_forest": {"n_estimators": 20, "max_depth": 3},
|
|
192
|
+
**({"xgboost": {"n_estimators": 20, "max_depth": 3}} if xgboost else {}),
|
|
193
|
+
},
|
|
179
194
|
},
|
|
180
195
|
"explainability": {"enabled": explain, "fragmentation": "brics", "top_n": 5},
|
|
181
196
|
"tml_explainability": {"enabled": explain, "top_n": 5},
|
|
@@ -223,8 +238,47 @@ def _check_psmiles() -> CheckResult:
|
|
|
223
238
|
return CheckResult("PSMILES canonicaliser", "PASS", "installed and working")
|
|
224
239
|
|
|
225
240
|
|
|
241
|
+
# Trains XGBoost the way the pipeline does: after PyTorch has been imported.
|
|
242
|
+
_XGBOOST_WITH_TORCH = """
|
|
243
|
+
import numpy as np
|
|
244
|
+
import torch # noqa: F401
|
|
245
|
+
import xgboost
|
|
246
|
+
X = np.random.default_rng(0).normal(size=(200, 10))
|
|
247
|
+
xgboost.XGBRegressor(n_estimators=10).fit(X, X[:, 0])
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _check_xgboost_with_torch() -> CheckResult:
|
|
252
|
+
"""Train XGBoost with PyTorch loaded, in a separate process that may crash."""
|
|
253
|
+
name = "XGBoost with PyTorch loaded"
|
|
254
|
+
try:
|
|
255
|
+
run = subprocess.run(
|
|
256
|
+
[sys.executable, "-c", _XGBOOST_WITH_TORCH], capture_output=True, text=True, timeout=300
|
|
257
|
+
)
|
|
258
|
+
except subprocess.TimeoutExpired:
|
|
259
|
+
return CheckResult(name, "FAIL", "did not finish within 5 minutes")
|
|
260
|
+
if run.returncode == 0:
|
|
261
|
+
return CheckResult(name, "PASS")
|
|
262
|
+
if run.returncode < 0: # killed by a signal, e.g. a segmentation fault
|
|
263
|
+
detail = f"crashed (signal {-run.returncode})"
|
|
264
|
+
if platform.system() == "Darwin":
|
|
265
|
+
detail += (
|
|
266
|
+
"; on macOS this is usually two OpenMP runtimes (PyTorch's and XGBoost's). "
|
|
267
|
+
'Install a compatible PyTorch: pip install "torch<2.11"'
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
detail = (run.stderr.strip().splitlines() or [f"exit code {run.returncode}"])[-1]
|
|
271
|
+
return CheckResult(name, "FAIL", detail + ". XGBoost is left out of the runs below.")
|
|
272
|
+
|
|
273
|
+
|
|
226
274
|
def _check_pipeline(
|
|
227
|
-
name: str,
|
|
275
|
+
name: str,
|
|
276
|
+
problem_type: str,
|
|
277
|
+
work_dir: Path,
|
|
278
|
+
epochs: int,
|
|
279
|
+
n_samples: int,
|
|
280
|
+
predict: bool,
|
|
281
|
+
xgboost: bool = True,
|
|
228
282
|
) -> CheckResult:
|
|
229
283
|
from polynet.pipeline.runner import main as run_pipeline
|
|
230
284
|
|
|
@@ -243,7 +297,9 @@ def _check_pipeline(
|
|
|
243
297
|
out_dir = run_dir / "results"
|
|
244
298
|
config_path = run_dir / "config.yaml"
|
|
245
299
|
with open(config_path, "w") as f:
|
|
246
|
-
yaml.safe_dump(
|
|
300
|
+
yaml.safe_dump(
|
|
301
|
+
check_config(problem_type, data_path, out_dir, epochs, predict_path, xgboost), f
|
|
302
|
+
)
|
|
247
303
|
|
|
248
304
|
collector = _ErrorCollector()
|
|
249
305
|
polynet_logger = logging.getLogger("polynet")
|
|
@@ -325,7 +381,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
325
381
|
|
|
326
382
|
results = [_check_packages()]
|
|
327
383
|
if results[0].status == "PASS":
|
|
328
|
-
results += [_check_device(), _check_psmiles()]
|
|
384
|
+
results += [_check_device(), _check_psmiles(), _check_xgboost_with_torch()]
|
|
385
|
+
xgboost = results[-1].status == "PASS"
|
|
329
386
|
|
|
330
387
|
work_dir = Path(tempfile.mkdtemp(prefix="polynet_check_"))
|
|
331
388
|
pipeline_checks = [
|
|
@@ -335,7 +392,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
335
392
|
for name, problem_type, predict in pipeline_checks:
|
|
336
393
|
print(f" running {name.lower()}…")
|
|
337
394
|
results.append(
|
|
338
|
-
_check_pipeline(
|
|
395
|
+
_check_pipeline(
|
|
396
|
+
name, problem_type, work_dir, args.epochs, args.samples, predict, xgboost
|
|
397
|
+
)
|
|
339
398
|
)
|
|
340
399
|
|
|
341
400
|
failed = any(r.status == "FAIL" for r in results)
|
|
@@ -458,7 +458,7 @@ def plot_bootstrap_boxplots(
|
|
|
458
458
|
fig, ax = plt.subplots(figsize=(height, width), dpi=dpi)
|
|
459
459
|
ax.boxplot(
|
|
460
460
|
data,
|
|
461
|
-
|
|
461
|
+
tick_labels=model_names, # `labels` was removed in Matplotlib 3.11
|
|
462
462
|
patch_artist=True,
|
|
463
463
|
boxprops=dict(facecolor=fill_colour, color=border_colour),
|
|
464
464
|
medianprops=dict(color=median_colour, linewidth=2),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "polynet"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.3"
|
|
4
4
|
description = "A Python package for polymer informatics: property prediction with graph neural networks and traditional machine learning"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Eduardo Aguilar-Bejarano", email = "eduardo.aguilar-bejarano@nottingham.ac.uk" },
|
|
@@ -19,7 +19,10 @@ dependencies = [
|
|
|
19
19
|
"pandas>=2.2.3,<3.0.0",
|
|
20
20
|
"rdkit>=2025.3.2,<2026.0.0",
|
|
21
21
|
"torch==2.2.0 ; sys_platform == 'darwin' and platform_machine == 'x86_64'",
|
|
22
|
-
|
|
22
|
+
# Apple silicon: from torch 2.11 the bundled libomp is no longer named like Homebrew's,
|
|
23
|
+
# so XGBoost loads a second OpenMP runtime and crashes once torch is imported.
|
|
24
|
+
"torch>=2.5.1,<2.11 ; sys_platform == 'darwin' and platform_machine == 'arm64'",
|
|
25
|
+
"torch>=2.5.1,<3.0.0 ; sys_platform != 'darwin'",
|
|
23
26
|
"torch-geometric==2.7",
|
|
24
27
|
"scikit-learn>=1.6.1,<2.0.0",
|
|
25
28
|
"matplotlib>=3.10.3,<4.0.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|