polynet 1.0.1__py3-none-any.whl

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.
Files changed (150) hide show
  1. polynet/__init__.py +8 -0
  2. polynet/app/Welcome_to_PolyNet.py +82 -0
  3. polynet/app/components/experiments.py +22 -0
  4. polynet/app/components/figure_export.py +87 -0
  5. polynet/app/components/forms/analyse_results.py +825 -0
  6. polynet/app/components/forms/applicability_domain.py +108 -0
  7. polynet/app/components/forms/create_experiment.py +501 -0
  8. polynet/app/components/forms/explain_model.py +684 -0
  9. polynet/app/components/forms/explain_tml.py +481 -0
  10. polynet/app/components/forms/explanation_plots.py +441 -0
  11. polynet/app/components/forms/fragment_plots.py +187 -0
  12. polynet/app/components/forms/plot_customiser.py +102 -0
  13. polynet/app/components/forms/representation.py +816 -0
  14. polynet/app/components/forms/search_grid.py +158 -0
  15. polynet/app/components/forms/train_models.py +1180 -0
  16. polynet/app/components/graph_display.py +197 -0
  17. polynet/app/components/optional_dependencies.py +50 -0
  18. polynet/app/components/plots.py +238 -0
  19. polynet/app/options/allowable_sets.py +3 -0
  20. polynet/app/options/file_paths.py +80 -0
  21. polynet/app/options/plot.py +21 -0
  22. polynet/app/options/state_keys.py +331 -0
  23. polynet/app/pages/1_Create_Experiment.py +108 -0
  24. polynet/app/pages/2_Representation.py +202 -0
  25. polynet/app/pages/3_Train_Models.py +560 -0
  26. polynet/app/pages/4_Predict.py +264 -0
  27. polynet/app/pages/5_Explain_Models.py +295 -0
  28. polynet/app/pages/6_Analyse_Results.py +203 -0
  29. polynet/app/services/cache.py +47 -0
  30. polynet/app/services/configurations.py +9 -0
  31. polynet/app/services/experiments.py +3 -0
  32. polynet/app/services/explain_model.py +282 -0
  33. polynet/app/services/explain_source.py +143 -0
  34. polynet/app/services/explain_tml.py +212 -0
  35. polynet/app/services/explanation_results.py +345 -0
  36. polynet/app/services/graph_viz.py +494 -0
  37. polynet/app/services/model_training.py +19 -0
  38. polynet/app/services/predict_model.py +3 -0
  39. polynet/app/services/progress.py +85 -0
  40. polynet/app/services/reruns.py +274 -0
  41. polynet/app/services/workload.py +54 -0
  42. polynet/app/static/polynet.png +0 -0
  43. polynet/app/utils/__init__.py +13 -0
  44. polynet/applicability/__init__.py +27 -0
  45. polynet/applicability/assess.py +458 -0
  46. polynet/applicability/calibration.py +111 -0
  47. polynet/applicability/domain.py +166 -0
  48. polynet/applicability/reference.py +159 -0
  49. polynet/applicability/similarity.py +140 -0
  50. polynet/cli.py +146 -0
  51. polynet/config/_loader.py +447 -0
  52. polynet/config/column_names.py +154 -0
  53. polynet/config/constants.py +64 -0
  54. polynet/config/display_names.py +148 -0
  55. polynet/config/enums.py +501 -0
  56. polynet/config/experiment.py +306 -0
  57. polynet/config/from_app.py +279 -0
  58. polynet/config/from_yaml.py +145 -0
  59. polynet/config/io.py +57 -0
  60. polynet/config/paths.py +379 -0
  61. polynet/config/schemas/__init__.py +47 -0
  62. polynet/config/schemas/applicability_domain.py +88 -0
  63. polynet/config/schemas/base.py +170 -0
  64. polynet/config/schemas/data.py +129 -0
  65. polynet/config/schemas/explainability.py +128 -0
  66. polynet/config/schemas/feature_preprocessing.py +101 -0
  67. polynet/config/schemas/fingerprints.py +174 -0
  68. polynet/config/schemas/general.py +47 -0
  69. polynet/config/schemas/plotting.py +59 -0
  70. polynet/config/schemas/representation.py +165 -0
  71. polynet/config/schemas/split_data.py +229 -0
  72. polynet/config/schemas/target_preprocessing.py +43 -0
  73. polynet/config/schemas/tml_explainability.py +116 -0
  74. polynet/config/schemas/training.py +473 -0
  75. polynet/config/search_grid.py +420 -0
  76. polynet/data/__init__.py +24 -0
  77. polynet/data/benchmarks/fluorine_mri_snr.csv +419 -0
  78. polynet/data/creator.py +193 -0
  79. polynet/data/feature_transformer.py +344 -0
  80. polynet/data/loader.py +245 -0
  81. polynet/data/preprocessing.py +406 -0
  82. polynet/data/sampling.py +134 -0
  83. polynet/data/structures.py +206 -0
  84. polynet/experiment/__init__.py +13 -0
  85. polynet/experiment/manager.py +50 -0
  86. polynet/explainability/__init__.py +124 -0
  87. polynet/explainability/attributions.py +37 -0
  88. polynet/explainability/embeddings.py +121 -0
  89. polynet/explainability/explain.py +959 -0
  90. polynet/explainability/fragment_figures.py +224 -0
  91. polynet/explainability/masking.py +635 -0
  92. polynet/explainability/selection.py +177 -0
  93. polynet/explainability/shap_explain.py +1359 -0
  94. polynet/explainability/visualization.py +850 -0
  95. polynet/factories/__init__.py +51 -0
  96. polynet/factories/dataloader.py +571 -0
  97. polynet/factories/loss.py +102 -0
  98. polynet/factories/network.py +187 -0
  99. polynet/factories/optimizer.py +218 -0
  100. polynet/featurizer/__init__.py +45 -0
  101. polynet/featurizer/allowable_sets.py +82 -0
  102. polynet/featurizer/descriptors.py +838 -0
  103. polynet/featurizer/graph.py +208 -0
  104. polynet/featurizer/pmx.py +450 -0
  105. polynet/featurizer/polymer_graph.py +508 -0
  106. polynet/featurizer/selection.py +278 -0
  107. polynet/inference/__init__.py +25 -0
  108. polynet/inference/ensemble.py +108 -0
  109. polynet/inference/gnn.py +157 -0
  110. polynet/inference/predict.py +154 -0
  111. polynet/inference/tml.py +155 -0
  112. polynet/inference/utils.py +108 -0
  113. polynet/models/__init__.py +66 -0
  114. polynet/models/base.py +665 -0
  115. polynet/models/gnn/__init__.py +40 -0
  116. polynet/models/gnn/cgcnn.py +248 -0
  117. polynet/models/gnn/cggnn.py +12 -0
  118. polynet/models/gnn/gat.py +161 -0
  119. polynet/models/gnn/gcn.py +211 -0
  120. polynet/models/gnn/graphsage.py +210 -0
  121. polynet/models/gnn/mpnn.py +230 -0
  122. polynet/models/gnn/transformer.py +163 -0
  123. polynet/models/persistence.py +237 -0
  124. polynet/pipeline/__init__.py +53 -0
  125. polynet/pipeline/runner.py +777 -0
  126. polynet/pipeline/self_check.py +348 -0
  127. polynet/pipeline/stages.py +1550 -0
  128. polynet/plotting/data_analysis.py +75 -0
  129. polynet/training/__init__.py +34 -0
  130. polynet/training/cv.py +55 -0
  131. polynet/training/evaluate.py +199 -0
  132. polynet/training/gnn.py +698 -0
  133. polynet/training/hyperopt.py +617 -0
  134. polynet/training/metrics.py +276 -0
  135. polynet/training/tml.py +477 -0
  136. polynet/utils/__init__.py +104 -0
  137. polynet/utils/chem_utils.py +383 -0
  138. polynet/utils/data_preprocessing.py +127 -0
  139. polynet/utils/graph_analysis.py +222 -0
  140. polynet/utils/optional_dependencies.py +91 -0
  141. polynet/utils/statistical_analysis.py +303 -0
  142. polynet/utils/validation.py +211 -0
  143. polynet/visualization/__init__.py +35 -0
  144. polynet/visualization/plots.py +480 -0
  145. polynet/visualization/utils.py +34 -0
  146. polynet-1.0.1.dist-info/METADATA +320 -0
  147. polynet-1.0.1.dist-info/RECORD +150 -0
  148. polynet-1.0.1.dist-info/WHEEL +4 -0
  149. polynet-1.0.1.dist-info/entry_points.txt +3 -0
  150. polynet-1.0.1.dist-info/licenses/LICENSE +21 -0
polynet/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """PolyNet: polymer property prediction with GNNs and traditional ML."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("polynet")
7
+ except PackageNotFoundError: # running from a source tree that is not installed
8
+ __version__ = "unknown"
@@ -0,0 +1,82 @@
1
+ from pathlib import Path
2
+
3
+ import streamlit as st
4
+
5
+ # Set page title and layout
6
+ st.set_page_config(page_title="PolyNet", page_icon="🧪")
7
+
8
+
9
+ # Main title
10
+ st.title("Welcome to PolyNet!")
11
+
12
+ # Load and display the logo
13
+ st.image(str(Path(__file__).parent / "static" / "polynet.png"), width=300)
14
+
15
+ # Description of the app
16
+ st.markdown(
17
+ """
18
+ **PolyNet** is a no-code platform for training and applying machine learning models to predict polymer properties from molecular structure.
19
+ It supports both **Graph Neural Networks (GNNs)**, which learn directly from molecular graphs, and **traditional ML models (TML)** such as Random Forest and Support Vector Machines, which operate on pre-computed molecular descriptors.
20
+ You can train either model family independently or both together and compare results.
21
+
22
+ ---
23
+
24
+ ### What PolyNet expects
25
+
26
+ Your dataset must be a **CSV file** where each row represents one polymer sample. The file must contain:
27
+
28
+ | Column | Required | Description |
29
+ |--------|----------|-------------|
30
+ | **SMILES column(s)** | Yes | One column per monomer unit (e.g. `SMILES_1`, `SMILES_2`). Both SMILES and pSMILES notation are supported. |
31
+ | **Target property column** | Yes | The property to predict. Use continuous numerical values for **regression** tasks (e.g. glass transition temperature), or integer/string class labels for **classification** tasks (e.g. biodegradable / not biodegradable). |
32
+ | **Sample ID column** | Recommended | A unique identifier per row (e.g. `polymer_id`). If omitted, row indices are used. |
33
+ | **Molar-ratio columns** | Optional | For copolymers, one column per monomer giving its molar ratio (e.g. `ratio_1`, `ratio_2`, `ratio_3`). Ratios are normalised per polymer to sum to 1 across the participating monomers, so any scale works (fractions, percentages, or arbitrary ratios such as 1:1:2); a ratio of 0 excludes that monomer. |
34
+ | **Extra descriptor columns** | Optional | Any additional numerical features (e.g. dispersity, mean molar mass) that SMILES alone cannot encode. These can be included as custom descriptors. |
35
+
36
+ #### Example CSV structure
37
+
38
+ A three-monomer copolymer with molar ratios (homopolymers/binary copolymers just set unused monomers to ratio 0):
39
+
40
+ | id | SMILES_1 | SMILES_2 | SMILES_3 | ratio_1 | ratio_2 | ratio_3 | Tg |
41
+ |-------|----------|----------|----------|---------|---------|---------|-------|
42
+ | P001 | CCO | CCC | c1ccccc1 | 1 | 1 | 2 | 120.5 |
43
+ | P002 | CCN | c1ccccc1 | CCO | 0.5 | 0.3 | 0.2 | 85.2 |
44
+ | ... | ... | ... | ... | ... | ... | ... | ... |
45
+
46
+ > **Note:** SMILES strings capture connectivity and atom types but cannot encode certain structural features such as dispersity, mean molar mass, or tacticity. Include these as extra columns if they are relevant to your property of interest.
47
+
48
+ ---
49
+
50
+ ### Workflow
51
+
52
+ The app is organised as a sequential pipeline. Work through the pages in order:
53
+
54
+ 1. **Create Experiment** — Name your experiment and select your data file. All outputs (models, results, plots) are saved under this experiment.
55
+
56
+ 2. **Representation** — Choose how to represent your molecules. Select molecular descriptor sets for TML models (e.g. Morgan fingerprints, RDKit descriptors) and/or graph node/edge features for GNNs.
57
+
58
+ 3. **Train Models** — Configure your data splitting strategy (random, scaffold, or bootstrap), then train TML models, GNN models, or both. Evaluation metrics and result plots are generated automatically.
59
+
60
+ 4. **Predict** — Upload a new CSV file (same column structure as your training data, target column optional) to obtain predictions from your trained models.
61
+
62
+ 5. **Explain Models** — Visualise which molecular fragments drive GNN predictions using attribution-based explainability methods.
63
+
64
+ 6. **Analyse Results** — Explore and compare model performance across metrics, splits, and model types.
65
+
66
+ ---
67
+
68
+ ### Get Started
69
+
70
+ Select **Create Experiment** from the left-hand menu to begin.
71
+ """
72
+ )
73
+
74
+ # Footer
75
+ st.markdown(
76
+ """
77
+ ---
78
+ If you encounter any issues or have suggestions, please reach out to: eduardo.aguilar-bejarano@nottingham.ac.uk
79
+
80
+ If you found PolyNet useful in your research, please consider citing our work:
81
+ """
82
+ )
@@ -0,0 +1,22 @@
1
+ import streamlit as st
2
+
3
+ from polynet.app.options.state_keys import ViewExperimentKeys
4
+
5
+
6
+ def experiment_selector(options: list) -> str:
7
+ """Select
8
+
9
+ Args:
10
+ options (list): The list of experiment names to choose from.
11
+
12
+ Returns:
13
+ str: The name of the experiment on disk.
14
+ """
15
+
16
+ return st.selectbox(
17
+ "Select an experiment",
18
+ options=options,
19
+ index=None,
20
+ placeholder="Experiment name",
21
+ key=ViewExperimentKeys.ExperimentName,
22
+ )
@@ -0,0 +1,87 @@
1
+ """Lossless and vector exports of the current figure, without inference."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from io import BytesIO
7
+ import re
8
+
9
+ import matplotlib as mpl
10
+ import streamlit as st
11
+
12
+ _MIMES = {"svg": "image/svg+xml", "pdf": "application/pdf", "png": "image/png"}
13
+
14
+
15
+ def figure_bytes(figure, file_format: str = "svg", dpi: int = 300) -> bytes:
16
+ """Export the supplied figure without altering its size, artists or data.
17
+
18
+ SVG embeds font outlines; PDF embeds TrueType fonts. PNG is lossless at the
19
+ requested DPI. Raster images already inside a figure retain their source detail.
20
+ """
21
+ if file_format not in _MIMES:
22
+ raise ValueError("Choose SVG, PDF or PNG for the plot export.")
23
+ if not isinstance(dpi, int) or isinstance(dpi, bool) or not 50 <= dpi <= 1200:
24
+ raise ValueError("Export DPI must be an integer between 50 and 1200.")
25
+ if file_format == "png":
26
+ width, height = figure.get_size_inches()
27
+ if (width + 0.2) * (height + 0.2) * dpi**2 > 40_000_000:
28
+ raise ValueError("PNG exceeds 40 million pixels. Lower DPI or export SVG/PDF.")
29
+ output = BytesIO()
30
+ # Matplotlib normally rasterises colourbars (and SHAP can rasterise points).
31
+ # Retain those artists as vectors for publication, restoring flags afterwards.
32
+ rasterized = [artist for artist in figure.findobj() if artist.get_rasterized()]
33
+ try:
34
+ if file_format in ("svg", "pdf"):
35
+ for artist in rasterized:
36
+ artist.set_rasterized(False)
37
+ with mpl.rc_context({"svg.fonttype": "path", "pdf.fonttype": 42}):
38
+ figure.savefig(
39
+ output,
40
+ format=file_format,
41
+ dpi=dpi,
42
+ bbox_inches="tight",
43
+ pad_inches=0.1,
44
+ facecolor="white",
45
+ )
46
+ finally:
47
+ for artist in rasterized:
48
+ artist.set_rasterized(True)
49
+ return output.getvalue()
50
+
51
+
52
+ def display_exportable_figure(
53
+ figure, *, container=st, export_options: dict | None = None, plot_id=("plot",)
54
+ ) -> None:
55
+ """Show a figure with a per-plot export button when page 6 enables exports.
56
+
57
+ The preview stays at screen resolution; only downloads use the chosen DPI.
58
+ Other callers omit ``export_options`` and keep their existing preview.
59
+ """
60
+ if export_options is None:
61
+ container.pyplot(figure, use_container_width=True)
62
+ return
63
+ container.pyplot(figure, use_container_width=True, dpi=150)
64
+ identity = hashlib.sha256(repr(plot_id).encode()).hexdigest()[:12]
65
+ key = f"{export_options['prefix']}_{identity}"
66
+ formats = {"svg": "SVG (vector)", "pdf": "PDF (vector)", "png": "PNG (lossless)"}
67
+ file_format = container.selectbox(
68
+ "Export format",
69
+ list(formats),
70
+ format_func=formats.get,
71
+ key=f"{key}_format",
72
+ help="SVG and PDF keep lines and text sharp at any size. Embedded molecule heatmaps retain their drawn resolution; PNG uses the selected export DPI.",
73
+ )
74
+ name = "-".join(str(part) for part in plot_id if part is not None)
75
+ name = re.sub(r"[^A-Za-z0-9_.-]+", "-", name).strip("-.") or "plot"
76
+ try:
77
+ data = figure_bytes(figure, file_format, export_options["dpi"])
78
+ container.download_button(
79
+ "Export plot",
80
+ data=data,
81
+ file_name=f"plot-{name}.{file_format}",
82
+ mime=_MIMES[file_format],
83
+ key=f"{key}_download",
84
+ on_click="ignore",
85
+ )
86
+ except (ValueError, RuntimeError, OSError) as exc:
87
+ container.warning(f"Unable to export this plot: {exc}")