Skip to content

Commit

Permalink
Fix concentration setting (#453)
Browse files Browse the repository at this point in the history
* Fix setting of concentrations

* Bump to 1.1.6

* minor GUI improvements

* Fix freezing with empty search string

* variable rename

* GUI improvements

* cleanup

* Add thermodynamic FBA

* Clean-up

* Fix clean-up

Co-authored-by: axelvonkamp <[email protected]>
  • Loading branch information
Paulocracy and axelvonkamp authored Jan 27, 2023
1 parent 2f5fd16 commit d563b14
Show file tree
Hide file tree
Showing 18 changed files with 156 additions and 51 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ Everyone is welcome to contribute to CNApy's development. [See our contribution
3. Create a conda environment with all dependencies

```sh
conda create -n cnapy-1.1.5 -c conda-forge -c cnapy cnapy=1.1.5
conda create -n cnapy-1.1.6 -c conda-forge -c cnapy cnapy=1.1.6
```

4. Activate the cnapy conda environment

```sh
conda activate cnapy-1.1.5
conda activate cnapy-1.1.6
```

5. Run CNApy within you activated conda environment
Expand Down
2 changes: 1 addition & 1 deletion cnapy/appdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class AppData:
''' The application data '''

def __init__(self):
self.version = "cnapy-1.1.5"
self.version = "cnapy-1.1.6"
self.format_version = 2
self.unsaved = False
self.project = ProjectData()
Expand Down
4 changes: 2 additions & 2 deletions cnapy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def make_scenario_feasible(cobra_model: cobra.Model, scen_values: Dict[str, Tupl
if v != 0:
print(s.name, format_string.format(v, v/abs(coeff)))
bm_mod[m] = v
if gam_mets_param is not None:
if len(gam_mets) > 0:
gam_adjust = gam_max_change * model.solver.variables["gam_slack"].primal
else:
for m,_,coeff,(s_p,s_n) in bm_coeff_var:
Expand All @@ -241,7 +241,7 @@ def make_scenario_feasible(cobra_model: cobra.Model, scen_values: Dict[str, Tupl
if v != 0:
print(s_n.name, format_string.format(v, v/abs(coeff)))
bm_mod[m] = -v
if gam_mets_param is not None:
if len(gam_mets) > 0:
gam_adjust = gam_max_change * \
(model.solver.variables["gam_slack_pos"].primal - model.solver.variables["gam_slack_neg"].primal)
if len(gam_mets) > 0:
Expand Down
3 changes: 3 additions & 0 deletions cnapy/gui_elements/central_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ def delete_map(self, idx: int):
def update_selected(self):
string = self.searchbar.text()

if string == "":
return

idx = self.tabs.currentIndex()
with_annotations = self.search_annotations.isChecked() and self.search_annotations.isEnabled()
if idx == ModelTabIndex.Reactions:
Expand Down
44 changes: 39 additions & 5 deletions cnapy/gui_elements/flux_feasibility_dialog.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from math import copysign
from qtpy.QtCore import Qt, Slot, QSignalBlocker
from qtpy.QtWidgets import (QDialog, QGroupBox, QHBoxLayout, QTableWidget, QCheckBox, QMainWindow,
QLabel, QLineEdit, QMessageBox, QPushButton, QAbstractItemView,
QRadioButton, QVBoxLayout, QTableWidgetItem, QButtonGroup, QStyledItemDelegate)
QLabel, QLineEdit, QMessageBox, QPushButton, QAbstractItemView, QAction,
QRadioButton, QVBoxLayout, QTableWidgetItem, QButtonGroup,
QStyledItemDelegate, QTableWidgetSelectionRange)
from qtpy.QtGui import QGuiApplication

from cnapy.utils import QComplReceivLineEdit
from cnapy.core import make_scenario_feasible, QPnotSupportedException
Expand Down Expand Up @@ -93,11 +95,16 @@ def __init__(self, main_window: QMainWindow):
self.bm_constituents.setSortingEnabled(True)
self.bm_constituents.verticalHeader().setVisible(False)
self.bm_constituents.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.bm_constituents.setSelectionMode(QAbstractItemView.ContiguousSelection)
self.bm_constituents.resizeColumnsToContents()
coefficient_delegate = CoefficientDelegate()
self.bm_constituents.setItemDelegateForColumn(3, coefficient_delegate)
self.bm_constituents.setItemDelegateForColumn(4, coefficient_delegate)
self.bm_constituents.setItemDelegateForColumn(5, coefficient_delegate)
copy_action = QAction("Copy", self.bm_constituents)
copy_action.setShortcut("Ctrl+C")
copy_action.triggered.connect(self.copy_table_selection)
self.bm_constituents.addAction(copy_action)
vbox_bm_coeff.addWidget(self.bm_constituents)
hbox = QHBoxLayout()
hbox.addWidget(QLabel("Maximal relative coefficient change [%]:"))
Expand Down Expand Up @@ -182,6 +189,12 @@ def compute(self):
abs_flux_weights: bool = False
weights_key: str = None

# clear previous results
self.gam_adjustment.setText("")
for i in range(self.bm_constituents.rowCount()):
self.bm_constituents.setItem(i, 4, None)
self.bm_constituents.setItem(i, 5, None)

if self.flux_group.isChecked():
if self.abs_flux_weights_button.isChecked():
abs_flux_weights = True
Expand Down Expand Up @@ -212,8 +225,6 @@ def compute(self):
checkbox = self.bm_constituents.cellWidget(i, 0)
if checkbox is not None and checkbox.isChecked():
variable_constituents.append(self.bm_constituents.item(i, 1).data(Qt.UserRole))
self.bm_constituents.setItem(i, 4, None)
self.bm_constituents.setItem(i, 5, None)
try:
max_coeff_change = float(self.max_coeff_change.text())
except ValueError:
Expand Down Expand Up @@ -333,7 +344,7 @@ def compute(self):
self.appdata.project.scen_values.reactions[self.bm_mod_reac_id] = \
[{met.id: coeff for met, coeff in bm_reac_mod.metabolites.items()}, fixed_growth_rate, fixed_growth_rate]
self.main_window.centralWidget().tabs.widget(ModelTabIndex.Scenario).recreate_scenario_items_needed = True
if self.adjust_gam.isChecked():
if self.bm_group.isChecked() and self.adjust_gam.isChecked():
self.gam_adjustment.setText("Calculated GAM adjustment: "+coefficient_format.format(gam_adjust))
self.main_window.centralWidget().update()
else:
Expand Down Expand Up @@ -409,3 +420,26 @@ def enable_biomass_equation_modifications(self, enable: bool):
self.bm_group.setChecked(enable)
self.adjust_bm_coeff.setEnabled(enable)
self.adjust_gam.setEnabled(enable)

@Slot()
def copy_table_selection(self):
selection_range: QTableWidgetSelectionRange = self.bm_constituents.selectedRanges()[0]
table = []
r = selection_range.topRow()
while r <= selection_range.bottomRow():
row = []
c = selection_range.leftColumn()
while c <= selection_range.rightColumn():
if c == 0:
row.append(str(self.bm_constituents.cellWidget(r, c).isChecked()))
else:
item: QTableWidgetItem = self.bm_constituents.item(r, c)
if item is None:
row.append("")
else:
row.append(str(item.data(Qt.DisplayRole)))
c += 1
table.append('\t'.join(row))
r += 1
clipboard = QGuiApplication.clipboard()
clipboard.setText('\r'.join(table))
30 changes: 27 additions & 3 deletions cnapy/gui_elements/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from cnapy.gui_elements.flux_optimization_dialog import FluxOptimizationDialog
from cnapy.gui_elements.configuration_cplex import CplexConfigurationDialog
from cnapy.gui_elements.configuration_gurobi import GurobiConfigurationDialog
from cnapy.gui_elements.thermodynamics_dialog import ThermodynamicDialog
from cnapy.gui_elements.thermodynamics_dialog import ThermodynamicAnalysisTypes, ThermodynamicDialog
import cnapy.utils as utils


Expand Down Expand Up @@ -398,6 +398,9 @@ def __init__(self, appdata: AppData):
optmdf_action.triggered.connect(self.perform_optmdfpathway)
self.analysis_menu.addAction(optmdf_action)

tfba_action = QAction("Thermodynamic FBA...", self)
tfba_action.triggered.connect(self.perform_thermodynamic_fba)
self.analysis_menu.addAction(tfba_action)

bottleneck_action = QAction("Thermodynamic bottleneck analysis...", self)
bottleneck_action.triggered.connect(self.perform_bottleneck_analysis)
Expand Down Expand Up @@ -1878,14 +1881,32 @@ def set_status_unknown(self):

@Slot()
def perform_optmdfpathway(self):
# Has to be in self to keep computation thread
self.optmdfpathway_dialog = ThermodynamicDialog(
self.appdata, self.centralWidget(), bottleneck_analysis=False)
self.appdata,
self.centralWidget(),
analysis_type=ThermodynamicAnalysisTypes.OPTMDFPATHWAY
)
self.optmdfpathway_dialog.exec_()

@Slot()
def perform_thermodynamic_fba(self):
# Has to be in self to keep computation thread
self.thermodynamic_fba_dialog = ThermodynamicDialog(
self.appdata,
self.centralWidget(),
analysis_type=ThermodynamicAnalysisTypes.THERMODYNAMIC_FBA
)
self.thermodynamic_fba_dialog.exec_()

@Slot()
def perform_bottleneck_analysis(self):
# Has to be in self to keep computation thread
self.bottleneck_dialog = ThermodynamicDialog(
self.appdata, self.centralWidget(), bottleneck_analysis=True)
self.appdata,
self.centralWidget(),
analysis_type=ThermodynamicAnalysisTypes.BOTTLENECK_ANALYSIS
)
self.bottleneck_dialog.exec_()

def _load_json(self) -> Dict[Any, Any]:
Expand Down Expand Up @@ -1933,6 +1954,9 @@ def _set_concentrations(self, concentrations):
else:
lb = concentrations[metabolite.id]["min"]
ub = concentrations[metabolite.id]["max"]

metabolite.annotation["Cmin"] = lb
metabolite.annotation["Cmax"] = ub
self.centralWidget().update()
self.unsaved_changes()

Expand Down
8 changes: 5 additions & 3 deletions cnapy/gui_elements/scenario_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(self, central_widget):
self.use_scenario_objective.setEnabled(True)
self.use_scenario_objective.stateChanged.connect(self.use_scenario_objective_changed)
self.objective_group_layout.addWidget(self.use_scenario_objective)
self.scenario_objective = QComplReceivLineEdit(self, [])
self.scenario_objective = QComplReceivLineEdit(self, [], reject_empty_string=False)
self.scenario_objective.set_wordlist(self.reaction_ids, replace_completer_model=False)
self.scenario_objective.set_completer_model(self.reaction_ids_model)
self.objective_group_layout.addWidget(self.scenario_objective)
Expand All @@ -55,6 +55,7 @@ def __init__(self, central_widget):
layout.addWidget(group)

label = QLabel("Scenario reactions")
label.setToolTip("The IDs of the scenario reactions must be distinct from those in the network.\nYou may introduce new metabolites in scenario reactions.")
hbox = QHBoxLayout()
hbox.addWidget(label)
self.add_reaction = QPushButton("+")
Expand All @@ -76,6 +77,7 @@ def __init__(self, central_widget):
layout.addWidget(self.equation)

label = QLabel("Scenario constraints")
label.setToolTip('Formulated as linear inequality constraints over the reactions,\ne.g. "R1 + R2 >= 10"\nmeans that the sum of fluxes through R1 and R2 must be at least 10.')
hbox = QHBoxLayout()
hbox.addWidget(label)
self.add_constraint_button = QPushButton("+")
Expand Down Expand Up @@ -407,13 +409,13 @@ def change_scenario_objective_coefficients(self, text_correct: bool):
self.objectiveSetupChanged.emit()
self.use_scenario_objective.setEnabled(True)
else:
self.appdata.project.scen_values.objective_coefficients.clear()
self.use_scenario_objective.setEnabled(False)

@Slot()
def validate_objective(self):
if not self.scenario_objective.is_valid and self.appdata.project.scen_values.use_scenario_objective:
self.use_scenario_objective.setChecked(False)
self.appdata.project.scen_values.objective_coefficients.clear()
self.use_scenario_objective.setChecked(False) # triggers use_scenario_objective_changed

@Slot(int)
def use_scenario_objective_changed(self, state: int):
Expand Down
Loading

0 comments on commit d563b14

Please sign in to comment.