Skip to content

Commit 206e930

Browse files
Default Values from SBML (#89)
* Get values from sbml model in case of Parameters * In max/min of parameter bounds, account for the value that is inserted * Updating data refined
1 parent 9aa46c1 commit 206e930

File tree

10 files changed

+151
-48
lines changed

10 files changed

+151
-48
lines changed

src/petab_gui/C.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
MIN_COLUMN = "use column min"
6868
MAX_COLUMN = "use column max"
6969
MODE = "use most frequent"
70+
SBML_LOOK = "sbml value"
7071
STRATEGIES_DEFAULT = [COPY_FROM, USE_DEFAULT, NO_DEFAULT]
7172
STRATEGIES_DEFAULT_EXT = STRATEGIES_DEFAULT + [MODE]
7273
STRATEGIES_DEFAULT_ALL = STRATEGIES_DEFAULT_EXT + [MIN_COLUMN, MAX_COLUMN]
@@ -77,6 +78,7 @@
7778
MIN_COLUMN: "Use the minimum value of the column",
7879
MAX_COLUMN: "Use the maximum value of the column",
7980
MODE: "Use the most frequent value of the column",
81+
SBML_LOOK: "Use the value from the SBML model",
8082
}
8183
SOURCE_COLUMN = "source_column"
8284
DEFAULT_VALUE = "default_value"
@@ -96,7 +98,7 @@
9698
"parameterScale": [USE_DEFAULT, NO_DEFAULT, MODE],
9799
"lowerBound": [MIN_COLUMN, MAX_COLUMN, USE_DEFAULT, NO_DEFAULT, MODE],
98100
"upperBound": [MAX_COLUMN, MAX_COLUMN, USE_DEFAULT, NO_DEFAULT, MODE],
99-
"nominalValue": [USE_DEFAULT, NO_DEFAULT],
101+
"nominalValue": [USE_DEFAULT, NO_DEFAULT, SBML_LOOK],
100102
"estimate": [USE_DEFAULT, NO_DEFAULT, MODE],
101103
}
102104
ALLOWED_STRATEGIES_COND = {
@@ -157,6 +159,9 @@
157159
"estimate": {
158160
"strategy": USE_DEFAULT, DEFAULT_VALUE: 1
159161
},
162+
"nominalValue": {
163+
"strategy": SBML_LOOK
164+
},
160165
}
161166
DEFAULT_COND_CONFIG = {
162167
"conditionId": {

src/petab_gui/commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def redo(self):
9999
position = df.shape[0] - 1 # insert *before* the auto-row
100100
self.model.beginInsertRows(QModelIndex(), position, position + len(self.row_indices) - 1)
101101
for i, idx in enumerate(self.row_indices):
102-
df.loc[idx] = [""] * df.shape[1]
102+
df.loc[idx] = [np.nan] * df.shape[1]
103103
self.model.endInsertRows()
104104
else:
105105
self.model.beginRemoveRows(QModelIndex(), min(self.row_indices), max(self.row_indices))

src/petab_gui/controllers/default_handler.py

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
from collections import Counter
77
from ..C import (COPY_FROM, USE_DEFAULT, NO_DEFAULT, MIN_COLUMN, MAX_COLUMN,
8-
MODE, DEFAULT_VALUE, SOURCE_COLUMN, STRATEGIES_DEFAULT)
8+
MODE, DEFAULT_VALUE, SOURCE_COLUMN, SBML_LOOK)
99

1010

1111
class DefaultHandlerModel:
12-
def __init__(self, model, config):
12+
def __init__(self, model, config, sbml_model = None):
1313
"""
1414
Initialize the handler for the model.
1515
:param model: The PandasTable Model containing the Data.
@@ -20,12 +20,21 @@ def __init__(self, model, config):
2020
self.model = model._data_frame
2121
self.config = config
2222
self.model_index = self.model.index.name
23+
self._sbml_model = sbml_model
2324

24-
def get_default(self, column_name, row_index=None):
25+
def get_default(
26+
self,
27+
column_name,
28+
row_index=None,
29+
par_scale=None,
30+
changed: dict | None = None,
31+
):
2532
"""
2633
Get the default value for a column based on its strategy.
2734
:param column_name: The name of the column to compute the default for.
2835
:param row_index: Optional index of the row (needed for some strategies).
36+
:param par_scale: Optional parameter scale (needed for some strategies).
37+
:param changed: Optional tuple containing the column name and index of the changed cell.
2938
:return: The computed default value.
3039
"""
3140
source_column = column_name
@@ -40,40 +49,64 @@ def get_default(self, column_name, row_index=None):
4049
default_value = column_config.get(DEFAULT_VALUE, "")
4150

4251
if strategy == USE_DEFAULT:
52+
if self.model.dtypes[column_name] == float:
53+
return float(default_value)
4354
return default_value
4455
elif strategy == NO_DEFAULT:
4556
return ""
4657
elif strategy == MIN_COLUMN:
47-
return self._min_column(column_name)
58+
return self._min_column(column_name, par_scale)
4859
elif strategy == MAX_COLUMN:
49-
return self._max_column(column_name)
60+
return self._max_column(column_name, par_scale)
5061
elif strategy == COPY_FROM:
51-
return self._copy_column(column_name, column_config, row_index)
62+
return self._copy_column(
63+
column_name, column_config, row_index, changed
64+
)
5265
elif strategy == MODE:
5366
column_config[SOURCE_COLUMN] = source_column
5467
return self._majority_vote(column_name, column_config)
68+
elif strategy == SBML_LOOK:
69+
return self._sbml_lookup(row_index)
5570
else:
5671
raise ValueError(f"Unknown strategy '{strategy}' for column '{column_name}'.")
5772

58-
def _min_column(self, column_name):
59-
if column_name in self.model:
60-
column_data = self.model[column_name].replace("", np.nan).dropna()
61-
if not column_data.empty:
62-
return column_data.min()
63-
return ""
73+
def _min_column(self, column_name, par_scale=None):
74+
if column_name not in self.model:
75+
return ""
76+
column_data = self.model[column_name].replace("", np.nan).dropna()
77+
if column_name in ["upperBound", "lowerBound"]:
78+
column_data = column_data.loc[
79+
self.model["parameterScale"] == par_scale
80+
]
81+
if not column_data.empty:
82+
return column_data.min()
6483

65-
def _max_column(self, column_name):
66-
if column_name in self.model:
67-
column_data = self.model[column_name].replace("", np.nan).dropna()
68-
if not column_data.empty:
69-
return column_data.max()
70-
return ""
84+
def _max_column(self, column_name, par_scale=None):
85+
if column_name not in self.model:
86+
return ""
87+
column_data = self.model[column_name].replace("", np.nan).dropna()
88+
if column_name in ["upperBound", "lowerBound"]:
89+
column_data = column_data.loc[
90+
self.model["parameterScale"] == par_scale
91+
]
92+
if not column_data.empty:
93+
return column_data.max()
7194

72-
def _copy_column(self, column_name, config, row_index):
95+
def _copy_column(
96+
self,
97+
column_name,
98+
config,
99+
row_index,
100+
changed: dict | None = None
101+
):
102+
"""Copy the value from another column in the same row."""
73103
source_column = config.get(SOURCE_COLUMN, column_name)
74104
source_column_valid = (
75105
source_column in self.model or source_column == self.model_index
76106
)
107+
if changed:
108+
if source_column in changed.keys():
109+
return changed[source_column]
77110
if source_column and source_column_valid and row_index is not None:
78111
prefix = config.get("prefix", "")
79112
if row_index in self.model.index:
@@ -100,3 +133,20 @@ def _majority_vote(self, column_name, config):
100133
value_counts = Counter(valid_values)
101134
return value_counts.most_common(1)[0][0]
102135
return ""
136+
137+
def _sbml_lookup(self, row_key):
138+
"""Use the most frequent value in the column as the default.
139+
140+
Defaults to last used value in case of a tie.
141+
"""
142+
if self._sbml_model is None:
143+
return 1
144+
if row_key is None:
145+
return 1
146+
curr_model = self._sbml_model.get_current_sbml_model()
147+
if curr_model is None:
148+
return 1
149+
parameters = curr_model.get_valid_parameters_for_parameter_table()
150+
if row_key not in list(parameters):
151+
return 1
152+
return curr_model.get_parameter_value(row_key)

src/petab_gui/controllers/logger_controller.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def log_message(self, message, color="black", loglevel=1):
3232
return
3333
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
3434
full_message = \
35-
f"[{timestamp}]\t <span style='color: {color};'>{message}</span>"
35+
(f"[{timestamp}]\t <span style='color: {color};'>"
36+
f"{message}</span>")
3637
for view in self.views:
3738
view.logger.append(full_message)
3839

src/petab_gui/controllers/mother_controller.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ def setup_connections(self):
186186
settings_manager.new_log_message.connect(
187187
self.logger.log_message
188188
)
189+
# Update Parameter SBML Model
190+
self.sbml_controller.overwritten_model.connect(
191+
self.parameter_controller.update_handler_sbml
192+
)
189193

190194
def setup_actions(self):
191195
"""Setup actions for the main controller."""

src/petab_gui/controllers/sbml_controller.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__(
3434
The main controller of the application. Needed for signal
3535
forwarding.
3636
"""
37+
super().__init__()
3738
self.view = view
3839
self.model = model
3940
self.logger = logger
@@ -119,7 +120,8 @@ def overwrite_sbml(self, file_path=None):
119120
self.view.antimony_text_edit.setPlainText(
120121
self.model.antimony_text
121122
)
122-
# self.overwritten_model.emit() # Deactivated for now. Discuss!
123+
124+
self.overwritten_model.emit()
123125
self.logger.log_message(
124126
"SBML model successfully opened and overwritten.",
125127
color="green"

src/petab_gui/controllers/table_controllers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,9 @@ def update_handler_model(self):
978978
"""Update the handler model."""
979979
self.model.default_handler.model = self.model._data_frame
980980

981+
def update_handler_sbml(self):
982+
self.model.default_handler._sbml_model = self.mother_controller.model.sbml
983+
981984
def setup_completers(self):
982985
"""Set completers for the parameter table."""
983986
table_view = self.view.table_view

src/petab_gui/controllers/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import functools
77
import pandas as pd
88
import re
9+
import html
910

1011
from ..settings_manager import settings_manager
1112
from ..C import COMMON_ERRORS
@@ -23,6 +24,7 @@ def wrapper(self, row_data: pd.DataFrame = None, row_name:
2324
return True
2425
except Exception as e:
2526
err_msg = filtered_error(e)
27+
err_msg = html.escape(err_msg)
2628
if additional_error_check:
2729
if "Missing parameter(s)" in err_msg:
2830
match = re.search(r"\{(.+?)\}", err_msg)

0 commit comments

Comments
 (0)