Skip to content

Commit 66ca12e

Browse files
anmolbhatia05Anmoljsnels-weigand
authored
PyParamGUI MVP (#5)
This MVP is made using - 1) Anywidget for creating a widget and syncing the state between the ecmascript module frontend and python backend 2) Pydantic for model schema and validation 3) Pyglotaran for various IO utilities --------- Co-authored-by: Anmol <anmol-2@Anmol-Bhatias-MacBook-Air.local> Co-authored-by: Joris Snellenburg <jsnel@users.noreply.github.com> Co-authored-by: Sebastian Weigand <s.weigand.phy@gmail.com>
1 parent 0d2751b commit 66ca12e

16 files changed

Lines changed: 1419 additions & 23 deletions

.cruft.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"context": {
66
"cookiecutter": {
77
"full_name": "Anmol Bhatia",
8-
"email": "a.bhatia2@student.vu.nl",
8+
"email": "anmolbhatia05@gmail.com",
99
"github_username": "glotaran",
1010
"project_name": "PyParamGUI",
1111
"project_slug": "pyparamgui",

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,9 @@ ENV/
104104

105105
# IDE settings
106106
.vscode/
107+
108+
examples/
109+
!examples/*.ipynb
110+
111+
# MacOs OS files
112+
.DS_store

.pre-commit-config.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ repos:
2020
args: [--fix=lf]
2121

2222
- repo: https://github.com/PyCQA/docformatter
23-
rev: v1.7.5
23+
rev: 06907d0
2424
hooks:
2525
- id: docformatter
2626
additional_dependencies: [tomli]
@@ -62,7 +62,7 @@ repos:
6262
hooks:
6363
- id: mypy
6464
exclude: "docs"
65-
additional_dependencies: ["types-all"]
65+
additional_dependencies: ["types-PyYAML"]
6666

6767
###################
6868
# LINTER DOCS #
@@ -74,13 +74,13 @@ repos:
7474
alias: flake8-docs
7575
args:
7676
- "--select=DOC"
77-
- "--extend-ignore=DOC502"
77+
- "--extend-ignore=DOC502,DOC601,DOC603,DOC101,DOC103,DOC201"
7878
- "--color=always"
7979
- "--require-return-section-when-returning-nothing=False"
8080
- "--allow-init-docstring=True"
8181
- "--skip-checking-short-docstrings=False"
8282
name: "flake8 lint docstrings"
83-
exclude: "^(docs/|tests?/)"
83+
exclude: "^(docs/|tests?/|pyparamgui/generator.py)"
8484
additional_dependencies: [pydoclint==0.5.3]
8585

8686
- repo: https://github.com/econchick/interrogate
@@ -97,6 +97,7 @@ repos:
9797
types: [file]
9898
types_or: [python, pyi, markdown, rst, jupyter]
9999
args: [-L nnumber]
100+
exclude: ^examples/
100101

101102
- repo: https://github.com/rhysd/actionlint
102103
rev: "v1.7.1"

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@
1414

1515
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
1616

17-
pyglotaran notebook widgets for teaching parameter estimation examples
18-
19-
## Features
20-
21-
- TODO
17+
A pyglotaran based jupyter notebook widget for teaching parameter estimation examples. It can simulate data, visualize it and create related model.yml, parameters.csv and dataset.nc files. It is supposed to help students learn about the basics of the pyglotaran ecosystem.
2218

2319
## Contributors ✨
2420

pyparamgui/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,17 @@
33
from __future__ import annotations
44

55
__author__ = """Anmol Bhatia"""
6-
__email__ = "a.bhatia2@student.vu.nl"
6+
__email__ = "anmolbhatia05@gmail.com"
77
__version__ = "0.0.1"
8+
9+
from pyparamgui.widget import Widget
10+
11+
__all__ = ["Widget"]
12+
"""
13+
Package Usage:
14+
%env ANYWIDGET_HMR=1
15+
from pyparamgui import Widget
16+
17+
widget = Widget()
18+
widget
19+
"""

pyparamgui/__main__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

pyparamgui/generator.py

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
"""The glotaran generator module."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
from typing import Any
7+
from typing import TypedDict
8+
from typing import cast
9+
10+
from glotaran.builtin.io.yml.utils import write_dict
11+
from glotaran.builtin.megacomplexes.decay import DecayParallelMegacomplex
12+
from glotaran.builtin.megacomplexes.decay import DecaySequentialMegacomplex
13+
from glotaran.builtin.megacomplexes.spectral import SpectralMegacomplex
14+
from glotaran.model import Model
15+
16+
if TYPE_CHECKING:
17+
from collections.abc import Callable
18+
19+
20+
def _generate_decay_model(
21+
*, nr_compartments: int, irf: bool, spectral: bool, decay_type: str
22+
) -> dict[str, Any]:
23+
"""Generate a decay model dictionary.
24+
25+
Parameters
26+
----------
27+
nr_compartments : int
28+
The number of compartments.
29+
irf : bool
30+
Whether to add a gaussian irf.
31+
spectral : bool
32+
Whether to add a spectral model.
33+
decay_type : str
34+
The type of the decay
35+
36+
Returns
37+
-------
38+
dict[str, Any]
39+
The generated model dictionary.
40+
"""
41+
compartments = [f"species_{i+1}" for i in range(nr_compartments)]
42+
rates = [f"rates.species_{i+1}" for i in range(nr_compartments)]
43+
44+
model: dict[str, Any] = {
45+
"megacomplex": {
46+
f"megacomplex_{decay_type}_decay": {
47+
"type": f"decay-{decay_type}",
48+
"compartments": compartments,
49+
"rates": rates,
50+
},
51+
},
52+
"dataset": {"dataset_1": {"megacomplex": [f"megacomplex_{decay_type}_decay"]}},
53+
}
54+
if spectral:
55+
model["megacomplex"] |= {
56+
"megacomplex_spectral": {
57+
"type": "spectral",
58+
"shape": {
59+
compartment: f"shape_species_{i+1}"
60+
for i, compartment in enumerate(compartments)
61+
},
62+
}
63+
}
64+
model["shape"] = {
65+
f"shape_species_{i+1}": {
66+
"type": "skewed-gaussian",
67+
"amplitude": f"shapes.species_{i+1}.amplitude",
68+
"location": f"shapes.species_{i+1}.location",
69+
"width": f"shapes.species_{i+1}.width",
70+
"skewness": f"shapes.species_{i+1}.skewness",
71+
}
72+
for i in range(nr_compartments)
73+
}
74+
model["dataset"]["dataset_1"] |= {
75+
"global_megacomplex": ["megacomplex_spectral"],
76+
"spectral_axis_inverted": True,
77+
"spectral_axis_scale": 1e7,
78+
}
79+
if irf:
80+
model["dataset"]["dataset_1"] |= {"irf": "gaussian_irf"}
81+
model["irf"] = {
82+
"gaussian_irf": {"type": "gaussian", "center": "irf.center", "width": "irf.width"},
83+
}
84+
return model
85+
86+
87+
def generate_parallel_decay_model(
88+
*, nr_compartments: int = 1, irf: bool = False
89+
) -> dict[str, Any]:
90+
"""Generate a parallel decay model dictionary.
91+
92+
Parameters
93+
----------
94+
nr_compartments : int
95+
The number of compartments.
96+
irf : bool
97+
Whether to add a gaussian irf.
98+
99+
Returns
100+
-------
101+
dict[str, Any]
102+
The generated model dictionary.
103+
"""
104+
return _generate_decay_model(
105+
nr_compartments=nr_compartments, irf=irf, spectral=False, decay_type="parallel"
106+
)
107+
108+
109+
def generate_parallel_spectral_decay_model(
110+
*, nr_compartments: int = 1, irf: bool = False
111+
) -> dict[str, Any]:
112+
"""Generate a parallel spectral decay model dictionary.
113+
114+
Parameters
115+
----------
116+
nr_compartments : int
117+
The number of compartments.
118+
irf : bool
119+
Whether to add a gaussian irf.
120+
121+
Returns
122+
-------
123+
dict[str, Any]
124+
The generated model dictionary.
125+
"""
126+
return _generate_decay_model(
127+
nr_compartments=nr_compartments, irf=irf, spectral=True, decay_type="parallel"
128+
)
129+
130+
131+
def generate_sequential_decay_model(
132+
*, nr_compartments: int = 1, irf: bool = False
133+
) -> dict[str, Any]:
134+
"""Generate a sequential decay model dictionary.
135+
136+
Parameters
137+
----------
138+
nr_compartments : int
139+
The number of compartments.
140+
irf : bool
141+
Whether to add a gaussian irf.
142+
143+
Returns
144+
-------
145+
dict[str, Any]
146+
The generated model dictionary.
147+
"""
148+
return _generate_decay_model(
149+
nr_compartments=nr_compartments, irf=irf, spectral=False, decay_type="sequential"
150+
)
151+
152+
153+
def generate_sequential_spectral_decay_model(
154+
*, nr_compartments: int = 1, irf: bool = False
155+
) -> dict[str, Any]:
156+
"""Generate a sequential spectral decay model dictionary.
157+
158+
Parameters
159+
----------
160+
nr_compartments : int
161+
The number of compartments.
162+
irf : bool
163+
Whether to add a gaussian irf.
164+
165+
Returns
166+
-------
167+
dict[str, Any]
168+
The generated model dictionary.
169+
"""
170+
return _generate_decay_model(
171+
nr_compartments=nr_compartments, irf=irf, spectral=True, decay_type="sequential"
172+
)
173+
174+
175+
generators: dict[str, Callable] = {
176+
"decay_parallel": generate_parallel_decay_model,
177+
"spectral_decay_parallel": generate_parallel_spectral_decay_model,
178+
"decay_sequential": generate_sequential_decay_model,
179+
"spectral_decay_sequential": generate_sequential_spectral_decay_model,
180+
}
181+
182+
available_generators: list[str] = list(generators.keys())
183+
184+
185+
class GeneratorArguments(TypedDict, total=False):
186+
"""Arguments used by ``generate_model`` and ``generate_model``.
187+
188+
Parameters
189+
----------
190+
nr_compartments : int
191+
The number of compartments.
192+
irf : bool
193+
Whether to add a gaussian irf.
194+
195+
See Also
196+
--------
197+
generate_model
198+
generate_model_yml
199+
"""
200+
201+
nr_compartments: int
202+
irf: bool
203+
204+
205+
def generate_model(*, generator_name: str, generator_arguments: GeneratorArguments) -> Model:
206+
"""Generate a model.
207+
208+
Parameters
209+
----------
210+
generator_name : str
211+
The generator to use.
212+
generator_arguments : GeneratorArguments
213+
Arguments for the generator.
214+
215+
Returns
216+
-------
217+
Model
218+
The generated model
219+
220+
See Also
221+
--------
222+
generate_parallel_decay_model
223+
generate_parallel_spectral_decay_model
224+
generate_sequential_decay_model
225+
generate_sequential_spectral_decay_model
226+
227+
Raises
228+
------
229+
ValueError
230+
Raised when an unknown generator is specified.
231+
"""
232+
if generator_name not in generators:
233+
msg = (
234+
f"Unknown model generator '{generator_name}'. "
235+
f"Known generators are: {list(generators.keys())}"
236+
)
237+
raise ValueError(msg)
238+
model = generators[generator_name](**generator_arguments)
239+
return Model.create_class_from_megacomplexes(
240+
[DecayParallelMegacomplex, DecaySequentialMegacomplex, SpectralMegacomplex]
241+
)(**model)
242+
243+
244+
def generate_model_yml(*, generator_name: str, generator_arguments: GeneratorArguments) -> str:
245+
"""Generate a model as yml string.
246+
247+
Parameters
248+
----------
249+
generator_name : str
250+
The generator to use.
251+
generator_arguments : GeneratorArguments
252+
Arguments for the generator.
253+
254+
Returns
255+
-------
256+
str
257+
The generated model yml string.
258+
259+
See Also
260+
--------
261+
generate_parallel_decay_model
262+
generate_parallel_spectral_decay_model
263+
generate_sequential_decay_model
264+
generate_sequential_spectral_decay_model
265+
266+
Raises
267+
------
268+
ValueError
269+
Raised when an unknown generator is specified.
270+
"""
271+
if generator_name not in generators:
272+
msg = (
273+
f"Unknown model generator '{generator_name}'. "
274+
f"Known generators are: {list(generators.keys())}"
275+
)
276+
raise ValueError(msg)
277+
model = generators[generator_name](**generator_arguments)
278+
return cast(str, write_dict(model))

0 commit comments

Comments
 (0)