Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/petab_gui/C.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
MIN_COLUMN = "use column min"
MAX_COLUMN = "use column max"
MODE = "use most frequent"
SBML_LOOK = "sbml value"
STRATEGIES_DEFAULT = [COPY_FROM, USE_DEFAULT, NO_DEFAULT]
STRATEGIES_DEFAULT_EXT = STRATEGIES_DEFAULT + [MODE]
STRATEGIES_DEFAULT_ALL = STRATEGIES_DEFAULT_EXT + [MIN_COLUMN, MAX_COLUMN]
Expand All @@ -77,6 +78,7 @@
MIN_COLUMN: "Use the minimum value of the column",
MAX_COLUMN: "Use the maximum value of the column",
MODE: "Use the most frequent value of the column",
SBML_LOOK: "Use the value from the SBML model",
}
SOURCE_COLUMN = "source_column"
DEFAULT_VALUE = "default_value"
Expand All @@ -96,7 +98,7 @@
"parameterScale": [USE_DEFAULT, NO_DEFAULT, MODE],
"lowerBound": [MIN_COLUMN, MAX_COLUMN, USE_DEFAULT, NO_DEFAULT, MODE],
"upperBound": [MAX_COLUMN, MAX_COLUMN, USE_DEFAULT, NO_DEFAULT, MODE],
"nominalValue": [USE_DEFAULT, NO_DEFAULT],
"nominalValue": [USE_DEFAULT, NO_DEFAULT, SBML_LOOK],
"estimate": [USE_DEFAULT, NO_DEFAULT, MODE],
}
ALLOWED_STRATEGIES_COND = {
Expand Down Expand Up @@ -157,6 +159,9 @@
"estimate": {
"strategy": USE_DEFAULT, DEFAULT_VALUE: 1
},
"nominalValue": {
"strategy": SBML_LOOK
},
}
DEFAULT_COND_CONFIG = {
"conditionId": {
Expand Down
2 changes: 1 addition & 1 deletion src/petab_gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def redo(self):
position = df.shape[0] - 1 # insert *before* the auto-row
self.model.beginInsertRows(QModelIndex(), position, position + len(self.row_indices) - 1)
for i, idx in enumerate(self.row_indices):
df.loc[idx] = [""] * df.shape[1]
df.loc[idx] = [np.nan] * df.shape[1]
self.model.endInsertRows()
else:
self.model.beginRemoveRows(QModelIndex(), min(self.row_indices), max(self.row_indices))
Expand Down
88 changes: 69 additions & 19 deletions src/petab_gui/controllers/default_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

from collections import Counter
from ..C import (COPY_FROM, USE_DEFAULT, NO_DEFAULT, MIN_COLUMN, MAX_COLUMN,
MODE, DEFAULT_VALUE, SOURCE_COLUMN, STRATEGIES_DEFAULT)
MODE, DEFAULT_VALUE, SOURCE_COLUMN, SBML_LOOK)


class DefaultHandlerModel:
def __init__(self, model, config):
def __init__(self, model, config, sbml_model = None):
"""
Initialize the handler for the model.
:param model: The PandasTable Model containing the Data.
Expand All @@ -20,12 +20,21 @@ def __init__(self, model, config):
self.model = model._data_frame
self.config = config
self.model_index = self.model.index.name
self._sbml_model = sbml_model

def get_default(self, column_name, row_index=None):
def get_default(
self,
column_name,
row_index=None,
par_scale=None,
changed: dict | None = None,
):
"""
Get the default value for a column based on its strategy.
:param column_name: The name of the column to compute the default for.
:param row_index: Optional index of the row (needed for some strategies).
:param par_scale: Optional parameter scale (needed for some strategies).
:param changed: Optional tuple containing the column name and index of the changed cell.
:return: The computed default value.
"""
source_column = column_name
Expand All @@ -40,40 +49,64 @@ def get_default(self, column_name, row_index=None):
default_value = column_config.get(DEFAULT_VALUE, "")

if strategy == USE_DEFAULT:
if self.model.dtypes[column_name] == float:
return float(default_value)
return default_value
elif strategy == NO_DEFAULT:
return ""
elif strategy == MIN_COLUMN:
return self._min_column(column_name)
return self._min_column(column_name, par_scale)
elif strategy == MAX_COLUMN:
return self._max_column(column_name)
return self._max_column(column_name, par_scale)
elif strategy == COPY_FROM:
return self._copy_column(column_name, column_config, row_index)
return self._copy_column(
column_name, column_config, row_index, changed
)
elif strategy == MODE:
column_config[SOURCE_COLUMN] = source_column
return self._majority_vote(column_name, column_config)
elif strategy == SBML_LOOK:
return self._sbml_lookup(row_index)
else:
raise ValueError(f"Unknown strategy '{strategy}' for column '{column_name}'.")

def _min_column(self, column_name):
if column_name in self.model:
column_data = self.model[column_name].replace("", np.nan).dropna()
if not column_data.empty:
return column_data.min()
return ""
def _min_column(self, column_name, par_scale=None):
if column_name not in self.model:
return ""
column_data = self.model[column_name].replace("", np.nan).dropna()
if column_name in ["upperBound", "lowerBound"]:
column_data = column_data.loc[
self.model["parameterScale"] == par_scale
]
if not column_data.empty:
return column_data.min()

def _max_column(self, column_name):
if column_name in self.model:
column_data = self.model[column_name].replace("", np.nan).dropna()
if not column_data.empty:
return column_data.max()
return ""
def _max_column(self, column_name, par_scale=None):
if column_name not in self.model:
return ""
column_data = self.model[column_name].replace("", np.nan).dropna()
if column_name in ["upperBound", "lowerBound"]:
column_data = column_data.loc[
self.model["parameterScale"] == par_scale
]
if not column_data.empty:
return column_data.max()

def _copy_column(self, column_name, config, row_index):
def _copy_column(
self,
column_name,
config,
row_index,
changed: dict | None = None
):
"""Copy the value from another column in the same row."""
source_column = config.get(SOURCE_COLUMN, column_name)
source_column_valid = (
source_column in self.model or source_column == self.model_index
)
if changed:
if source_column in changed.keys():
return changed[source_column]
if source_column and source_column_valid and row_index is not None:
prefix = config.get("prefix", "")
if row_index in self.model.index:
Expand All @@ -100,3 +133,20 @@ def _majority_vote(self, column_name, config):
value_counts = Counter(valid_values)
return value_counts.most_common(1)[0][0]
return ""

def _sbml_lookup(self, row_key):
"""Use the most frequent value in the column as the default.

Defaults to last used value in case of a tie.
"""
if self._sbml_model is None:
return 1
if row_key is None:
return 1
curr_model = self._sbml_model.get_current_sbml_model()
if curr_model is None:
return 1
parameters = curr_model.get_valid_parameters_for_parameter_table()
if row_key not in list(parameters):
return 1
return curr_model.get_parameter_value(row_key)
3 changes: 2 additions & 1 deletion src/petab_gui/controllers/logger_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def log_message(self, message, color="black", loglevel=1):
return
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
full_message = \
f"[{timestamp}]\t <span style='color: {color};'>{message}</span>"
(f"[{timestamp}]\t <span style='color: {color};'>"
f"{message}</span>")
for view in self.views:
view.logger.append(full_message)

Expand Down
4 changes: 4 additions & 0 deletions src/petab_gui/controllers/mother_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ def setup_connections(self):
settings_manager.new_log_message.connect(
self.logger.log_message
)
# Update Parameter SBML Model
self.sbml_controller.overwritten_model.connect(
self.parameter_controller.update_handler_sbml
)

def setup_actions(self):
"""Setup actions for the main controller."""
Expand Down
4 changes: 3 additions & 1 deletion src/petab_gui/controllers/sbml_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__(
The main controller of the application. Needed for signal
forwarding.
"""
super().__init__()
self.view = view
self.model = model
self.logger = logger
Expand Down Expand Up @@ -119,7 +120,8 @@ def overwrite_sbml(self, file_path=None):
self.view.antimony_text_edit.setPlainText(
self.model.antimony_text
)
# self.overwritten_model.emit() # Deactivated for now. Discuss!

self.overwritten_model.emit()
self.logger.log_message(
"SBML model successfully opened and overwritten.",
color="green"
Expand Down
3 changes: 3 additions & 0 deletions src/petab_gui/controllers/table_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,9 @@ def update_handler_model(self):
"""Update the handler model."""
self.model.default_handler.model = self.model._data_frame

def update_handler_sbml(self):
self.model.default_handler._sbml_model = self.mother_controller.model.sbml

def setup_completers(self):
"""Set completers for the parameter table."""
table_view = self.view.table_view
Expand Down
2 changes: 2 additions & 0 deletions src/petab_gui/controllers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import functools
import pandas as pd
import re
import html

from ..settings_manager import settings_manager
from ..C import COMMON_ERRORS
Expand All @@ -23,6 +24,7 @@ def wrapper(self, row_data: pd.DataFrame = None, row_name:
return True
except Exception as e:
err_msg = filtered_error(e)
err_msg = html.escape(err_msg)
if additional_error_check:
if "Missing parameter(s)" in err_msg:
match = re.search(r"\{(.+?)\}", err_msg)
Expand Down
Loading