Skip to content

Commit

Permalink
more consistent awareness of scenario reactions
Browse files Browse the repository at this point in the history
better window management of strain design dialogs
  • Loading branch information
axelvonkamp committed Jul 10, 2024
1 parent 93d78cf commit 67d7b8b
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 122 deletions.
39 changes: 38 additions & 1 deletion cnapy/appdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import cobra
from optlang.symbolics import Zero
from optlang_enumerator.cobra_cnapy import CNApyModel
from qtpy.QtCore import Qt, Signal, QObject
from qtpy.QtCore import Qt, Signal, QObject, QStringListModel
from qtpy.QtGui import QColor, QFont
from qtpy.QtWidgets import QMessageBox

Expand Down Expand Up @@ -358,6 +358,39 @@ def clear(self):
super().clear()
self.__init__()

class IDList(object):
"""
provides a list of identifiers (id_list) and a corresponding QStringListModel (ids_model)
the identifiers can be set with the set_ids method
the implementation guarantees that id() of the properties id_list and ids_model
is constant so that they can be used like const references
"""
def __init__(self):
self._id_list: list = []
self._ids_model: QStringListModel = QStringListModel()

def set_ids(self, *id_lists: List[str]):
self._id_list.clear()
for id_list in id_lists:
self._id_list[len(self._id_list):] = id_list
self._ids_model.setStringList(self._id_list)

@property # getter only
def id_list(self) -> List[str]:
return self._id_list

@property # getter only
def ids_model(self) -> QStringListModel:
return self._ids_model

def replace_entry(self, old: str, new: str):
idx = self._id_list.index(old)
self._id_list[idx] = new
self._ids_model.setData(self._ids_model.index(idx), new)

def __len__(self) -> int:
return len(self._id_list)

class ProjectData:
''' The cnapy project data '''

Expand Down Expand Up @@ -389,6 +422,7 @@ def __init__(self):
msgBox.exec()

self.cobra_py_model = CNApyModel()
self.reaction_ids: IDList = IDList() # reaction IDs of the cobra_py_model and scenario reactions

default_map = CnaMap("Map")
self.maps = {"Map": default_map}
Expand Down Expand Up @@ -468,6 +502,9 @@ def collect_default_scenario_values(self) -> Tuple[List[str], List[Tuple[float,
values.append(parse_scenario(r.annotation['cnapy-default']))
return reactions, values

def update_reaction_id_lists(self):
self.reaction_ids.set_ids(self.cobra_py_model.reactions.list_attr("id"), self.scen_values.reactions.keys())

# currently unused
# def scenario_hash_value(self):
# return hashlib.md5(pickle.dumps(sorted(self.scen_values.items()))).digest()
Expand Down
5 changes: 4 additions & 1 deletion cnapy/gui_elements/central_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,12 @@ def handle_changed_reaction(self, previous_id: str, reaction: cobra.Reaction):
self.appdata.project.maps[mmap]["boxes"][reaction.id] = self.appdata.project.maps[mmap]["boxes"].pop(
previous_id)
reaction_has_box = True
if self.appdata.project.maps[mmap]["view"] == "escher":
if self.appdata.project.maps[mmap].get('view', '') == "escher":
escher_map_present = True
if reaction_has_box or escher_map_present:
self.update_reaction_on_maps(previous_id, reaction.id, reaction_has_box, escher_map_present)
if reaction.id != previous_id:
self.appdata.project.reaction_ids.replace_entry(previous_id, reaction.id)
self.update_item_in_history(previous_id, reaction.id, reaction.name, ModelItemType.Reaction)

def handle_deleted_reaction(self, reaction: cobra.Reaction):
Expand All @@ -203,6 +205,7 @@ def handle_deleted_reaction(self, reaction: cobra.Reaction):
if reaction.id in self.appdata.project.maps[mmap]["boxes"].keys():
self.appdata.project.maps[mmap]["boxes"].pop(reaction.id)
self.delete_reaction_on_maps(reaction.id)
self.appdata.project.update_reaction_id_lists()

if self.appdata.auto_fba:
self.parent.fba()
Expand Down
2 changes: 1 addition & 1 deletion cnapy/gui_elements/flux_feasibility_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(self, main_window: QMainWindow):
hbox = QHBoxLayout()
self.bm_label = QLabel("Biomass reaction: ")
hbox.addWidget(self.bm_label)
self.bm_reac_id_select = QComplReceivLineEdit(self, self.appdata.project.cobra_py_model.reactions.list_attr("id"))
self.bm_reac_id_select = QComplReceivLineEdit(self, self.appdata.project.reaction_ids)
self.bm_reac_id_select.setPlaceholderText("Choose a biomass reaction")
self.bm_reac_id_select.textCorrect.connect(self.verify_biomass_reaction)
hbox.addWidget(self.bm_reac_id_select)
Expand Down
12 changes: 6 additions & 6 deletions cnapy/gui_elements/flux_optimization_dialog.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""The cnapy flux optimization dialog"""
from random import randint
from numpy import isnan, isinf
from numpy import isinf
import re
from qtpy.QtCore import Qt, Signal, Slot
from qtpy.QtCore import Qt, Slot
from qtpy.QtWidgets import (QDialog, QHBoxLayout, QLabel, QComboBox,
QMessageBox, QPushButton, QVBoxLayout, QFrame)
QMessageBox, QPushButton, QVBoxLayout)

from cnapy.appdata import AppData
from cnapy.gui_elements.central_widget import CentralWidget
from cnapy.utils import QComplReceivLineEdit, QHSeperationLine
from cnapy.utils import QComplReceivLineEdit
from straindesign import fba, linexpr2dict, linexprdict2str, avail_solvers
from straindesign.names import *

Expand All @@ -23,7 +23,7 @@ def __init__(self, appdata: AppData, central_widget: CentralWidget):
self.central_widget = central_widget

numr = len(self.appdata.project.cobra_py_model.reactions)
self.reac_ids = self.appdata.project.cobra_py_model.reactions.list_attr("id")
self.reac_ids = self.appdata.project.reaction_ids.id_list
if numr > 1:
r1 = self.appdata.project.cobra_py_model.reactions[randint(0,numr-1)].id
else:
Expand All @@ -45,7 +45,7 @@ def __init__(self, appdata: AppData, central_widget: CentralWidget):
open_bracket.setFont(font)
editor_layout.addWidget(open_bracket)
flux_expr_layout = QVBoxLayout()
self.expr = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.expr = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.expr.setPlaceholderText('flux expression (e.g. 1.0 '+r1+')')
flux_expr_layout.addWidget(self.expr)
editor_layout.addItem(flux_expr_layout)
Expand Down
11 changes: 7 additions & 4 deletions cnapy/gui_elements/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,12 +688,12 @@ def strain_design_with_setup(self, sd_setup):
@Slot(str)
def compute_strain_design(self,sd_setup):
# launch progress viewer and computation thread
self.sd_viewer = SDComputationViewer(self.appdata, sd_setup)
self.sd_viewer = SDComputationViewer(self, self.appdata, sd_setup)
self.sd_viewer.show_sd_signal.connect(self.show_strain_designs_with_setup, Qt.QueuedConnection)
# connect signals to update progress
self.sd_computation = SDComputationThread(self.appdata, sd_setup)
self.sd_computation.output_connector.connect( self.sd_viewer.receive_progress_text,Qt.QueuedConnection)
self.sd_computation.finished_computation.connect( self.sd_viewer.conclude_computation, Qt.QueuedConnection)
self.sd_computation.output_connector.connect(self.sd_viewer.receive_progress_text, Qt.QueuedConnection)
self.sd_computation.finished_computation.connect(self.sd_viewer.conclude_computation, Qt.QueuedConnection)
self.sd_viewer.cancel_computation.connect(self.terminate_strain_design_computation)
# show dialog and launch process
# self.sd_viewer.exec()
Expand Down Expand Up @@ -887,6 +887,7 @@ def load_scenario_file(self, filename, merge=False):
self.appdata.project.comp_values.clear()
self.appdata.project.fva_values.clear()
self.central_widget.tabs.widget(ModelTabIndex.Scenario).recreate_scenario_items()
self.appdata.project.update_reaction_id_lists()

if len(missing_reactions) > 0 :
QMessageBox.warning(self, 'Unknown reactions in scenario',
Expand Down Expand Up @@ -1368,6 +1369,9 @@ def close_project_dialogs(self):
if self.sd_dialog is not None:
self.sd_dialog.close()
self.sd_dialog = None
if self.sd_sols is not None:
self.sd_sols.close()
self.sd_sols = None
if self.make_scenario_feasible_dialog is not None:
self.make_scenario_feasible_dialog.close()
self.make_scenario_feasible_dialog = None
Expand Down Expand Up @@ -1678,7 +1682,6 @@ def make_scenario_feasible(self):
self.make_scenario_feasible_dialog = FluxFeasibilityDialog(self)
else:
self.make_scenario_feasible_dialog.modified_scenario = None
self.make_scenario_feasible_dialog.bm_reac_id_select.set_wordlist(self.appdata.project.cobra_py_model.reactions.list_attr("id"))
self.make_scenario_feasible_dialog.show()

def fba_optimize_reaction(self, reaction: str, mmin: bool):
Expand Down
4 changes: 2 additions & 2 deletions cnapy/gui_elements/mode_navigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ def __init__(self, appdata: AppData, parent):
self.appdata = appdata
self.parent = parent

self.reac_ids = list(self.appdata.project.comp_values.keys()) #self.appdata.project.cobra_py_model.reactions.list_attr("id")
numr = len(self.reac_ids) # len(self.appdata.project.cobra_py_model.reactions)
self.reac_ids = list(self.appdata.project.comp_values.keys())
numr = len(self.reac_ids)
if numr > 1:
r1 = self.reac_ids[randint(0, numr-1)]
else:
Expand Down
19 changes: 9 additions & 10 deletions cnapy/gui_elements/plot_space_dialog.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""The flux space plot dialog"""

from random import randint
from qtpy.QtCore import Qt, Signal, Slot, QTimer
from qtpy.QtCore import Qt
from qtpy.QtWidgets import (QDialog, QHBoxLayout, QLabel, QMessageBox, QGroupBox, QComboBox, QLayout,
QPushButton, QVBoxLayout, QFrame, QCheckBox,QLineEdit, QSizePolicy)
QPushButton, QVBoxLayout, QFrame, QCheckBox,QLineEdit)
from cnapy.utils import QComplReceivLineEdit, QHSeperationLine
from straindesign import linexpr2dict, linexprdict2str, yopt, avail_solvers, plot_flux_space
from straindesign import plot_flux_space
from straindesign.names import *

class PlotSpaceDialog(QDialog):
Expand All @@ -19,7 +19,6 @@ def __init__(self, appdata):
self.appdata = appdata

numr = len(self.appdata.project.cobra_py_model.reactions)
self.reac_ids = self.appdata.project.cobra_py_model.reactions.list_attr("id")
self.r = ["" for _ in range(6)]
if numr > 5:
self.r = [self.appdata.project.cobra_py_model.reactions[randint(0,numr-1)].id for _ in self.r]
Expand Down Expand Up @@ -59,9 +58,9 @@ def __init__(self, appdata):
self.x_combobox.insertItem(1,'yield')
self.x_combobox.currentTextChanged.connect(self.x_combo_changed)
x_num_den_layout.addWidget(self.x_combobox)
self.x_numerator = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.x_numerator = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.x_numerator.setPlaceholderText('flux rate or expression (e.g. 1.0 '+self.r[0]+')')
self.x_denominator = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.x_denominator = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.x_denominator.setPlaceholderText('denominator (e.g. 1.0 '+self.r[1]+')')
x_num_den_layout.addWidget(self.x_numerator)
self.x_denominator.setHidden(True)
Expand All @@ -83,9 +82,9 @@ def __init__(self, appdata):
self.y_combobox.insertItem(1,'yield')
self.y_combobox.currentTextChanged.connect(self.y_combo_changed)
y_num_den_layout.addWidget(self.y_combobox)
self.y_numerator = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.y_numerator = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.y_numerator.setPlaceholderText('flux rate or expression (e.g. '+self.r[2]+')')
self.y_denominator = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.y_denominator = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.y_denominator.setPlaceholderText('denominator (e.g. '+self.r[3]+')')
y_num_den_layout.addWidget(self.y_numerator)
self.y_denominator.setHidden(True)
Expand All @@ -107,9 +106,9 @@ def __init__(self, appdata):
self.z_combobox.insertItem(1,'yield')
self.z_combobox.currentTextChanged.connect(self.z_combo_changed)
z_num_den_layout.addWidget(self.z_combobox)
self.z_numerator = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.z_numerator = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.z_numerator.setPlaceholderText('flux rate or expression (e.g. '+self.r[4]+')')
self.z_denominator = QComplReceivLineEdit(self,self.reac_ids,check=True)
self.z_denominator = QComplReceivLineEdit(self, self.appdata.project.reaction_ids, check=True)
self.z_denominator.setPlaceholderText('denominator (e.g. '+self.r[5]+')')
z_num_den_layout.addWidget(self.z_numerator)
self.z_denominator.setHidden(True)
Expand Down
1 change: 1 addition & 0 deletions cnapy/gui_elements/reactions_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ def add_new_reaction(self):
break
reaction = cobra.Reaction(name)
self.appdata.project.cobra_py_model.add_reactions([reaction])
self.appdata.project.update_reaction_id_lists()
reaction.set_hash_value()
self.appdata.project.cobra_py_model.set_stoichiometry_hash_object()
self.reaction_list.blockSignals(True)
Expand Down
Loading

0 comments on commit 67d7b8b

Please sign in to comment.