1# -*- coding: utf-8 -*-
2
3#-------------------------------------------------------------------------------
4
5# This file is part of Code_Saturne, a general-purpose CFD tool.
6#
7# Copyright (C) 1998-2021 EDF S.A.
8#
9# This program is free software; you can redistribute it and/or modify it under
10# the terms of the GNU General Public License as published by the Free Software
11# Foundation; either version 2 of the License, or (at your option) any later
12# version.
13#
14# This program is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17# details.
18#
19# You should have received a copy of the GNU General Public License along with
20# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
21# Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
23#-------------------------------------------------------------------------------
24
25"""
26This module defines the 'Equation parameters' page.
27
28This module contains the following classes and function:
29- SchemeDelegate
30- SolverDelegate
31- PrecisionDelegate
32- IterationDelegate
33- StandardItemModelScheme
34- StandardItemModelSolver
35- NumericalParamEquationView
36"""
37
38#-------------------------------------------------------------------------------
39# Library modules import
40#-------------------------------------------------------------------------------
41
42import logging
43
44#-------------------------------------------------------------------------------
45# Third-party modules
46#-------------------------------------------------------------------------------
47
48from code_saturne.Base.QtCore    import *
49from code_saturne.Base.QtGui     import *
50from code_saturne.Base.QtWidgets import *
51
52#-------------------------------------------------------------------------------
53# Application modules import
54#-------------------------------------------------------------------------------
55
56from code_saturne.model.Common import GuiParam
57from code_saturne.Base.QtPage import ComboModel, DoubleValidator, IntValidator
58from code_saturne.Base.QtPage import from_qvariant, to_text_string
59from NumericalParamEquationNeptune import Ui_NumericalParamEquation
60from code_saturne.model.NumericalParamEquationModelNeptune import NumericalParamEquatModel
61from code_saturne.model.GlobalNumericalParametersModel import GlobalNumericalParametersModel
62
63#-------------------------------------------------------------------------------
64# log config
65#-------------------------------------------------------------------------------
66
67logging.basicConfig()
68log = logging.getLogger("NumericalParamEquationView")
69log.setLevel(GuiParam.DEBUG)
70
71
72#-------------------------------------------------------------------------------
73# Combo box delegate for the scheme
74#-------------------------------------------------------------------------------
75
76class SchemeDelegate(QItemDelegate):
77    """
78    Use of a combo box in the table.
79    """
80    def __init__(self, parent, dicoM2V, dicoV2M):
81        super(SchemeDelegate, self).__init__(parent)
82        self.parent   = parent
83        self.dicoM2V  = dicoM2V
84        self.dicoV2M  = dicoV2M
85
86
87    def createEditor(self, parent, option, index):
88        editor = QComboBox(parent)
89        self.modelCombo = ComboModel(editor, 3, 1)
90        self.modelCombo.addItem(self.tr(self.dicoM2V["centered"]), 'centered')
91        self.modelCombo.addItem(self.tr(self.dicoM2V["upwind"]), 'upwind')
92        self.modelCombo.addItem(self.tr(self.dicoM2V["solu"]), 'solu')
93
94        editor.installEventFilter(self)
95        return editor
96
97
98    def setEditorData(self, comboBox, index):
99        row = index.row()
100        col = index.column()
101        string = index.model().getData(index)[col]
102        self.modelCombo.setItem(str_view=string)
103
104
105    def setModelData(self, comboBox, model, index):
106        txt = str(comboBox.currentText())
107        value = self.modelCombo.dicoV2M[txt]
108        log.debug("SchemeDelegate value = %s"%value)
109
110        selectionModel = self.parent.selectionModel()
111        for idx in selectionModel.selectedIndexes():
112            if idx.column() == index.column():
113                model.setData(idx, self.dicoM2V[value], Qt.DisplayRole)
114
115
116#-------------------------------------------------------------------------------
117# Combo box delegate for the solver
118#-------------------------------------------------------------------------------
119
120class SolverDelegate(QItemDelegate):
121    """
122    Use of a combo box in the table.
123    """
124    def __init__(self, parent, dicoM2V, dicoV2M):
125        super(SolverDelegate, self).__init__(parent)
126        self.parent   = parent
127        self.dicoM2V  = dicoM2V
128        self.dicoV2M  = dicoV2M
129
130
131    def createEditor(self, parent, option, index):
132        editor = QComboBox(parent)
133        self.modelCombo = ComboModel(editor, 12, 1)
134        self.modelCombo.addItem(self.tr(self.dicoM2V["automatic"]), 'automatic')
135        self.modelCombo.addItem(self.tr(self.dicoM2V["jacobi"]), 'jacobi')
136        self.modelCombo.addItem(self.tr(self.dicoM2V["pcg"]), 'pcg')
137        self.modelCombo.addItem(self.tr(self.dicoM2V["cgstab"]), 'cgstab')
138        self.modelCombo.addItem(self.tr(self.dicoM2V["jacobi_saturne"]), 'jacobi_saturne')
139        self.modelCombo.addItem(self.tr(self.dicoM2V["pcg_saturne"]), 'pcg_saturne')
140        self.modelCombo.addItem(self.tr(self.dicoM2V["bicgstab_saturne"]), 'bicgstab_saturne')
141        self.modelCombo.addItem(self.tr(self.dicoM2V["bicgstab2_saturne"]), 'bicgstab2_saturne')
142        self.modelCombo.addItem(self.tr(self.dicoM2V["gmres_saturne"]), 'gmres_saturne')
143        self.modelCombo.addItem(self.tr(self.dicoM2V["gauss_seidel_saturne"]), 'gauss_seidel_saturne')
144        self.modelCombo.addItem(self.tr(self.dicoM2V["sym_gauss_seidel_saturne"]), 'sym_gauss_seidel_saturne')
145        self.modelCombo.addItem(self.tr(self.dicoM2V["pcr3_saturne"]), 'pcr3_saturne')
146
147        editor.installEventFilter(self)
148        return editor
149
150
151    def setEditorData(self, comboBox, index):
152        row = index.row()
153        col = index.column()
154        string = index.model().getData(index)[col]
155        self.modelCombo.setItem(str_view=string)
156
157
158    def setModelData(self, comboBox, model, index):
159        txt = str(comboBox.currentText())
160        value = self.modelCombo.dicoV2M[txt]
161        log.debug("SolverDelegate value = %s"%value)
162
163        selectionModel = self.parent.selectionModel()
164        for idx in selectionModel.selectedIndexes():
165            if idx.column() == index.column():
166                model.setData(idx, self.dicoM2V[value], Qt.DisplayRole)
167
168
169#-------------------------------------------------------------------------------
170# Line edit delegate for the value
171#-------------------------------------------------------------------------------
172
173class PrecisionDelegate(QItemDelegate):
174    def __init__(self, parent=None):
175        super(PrecisionDelegate, self).__init__(parent)
176        self.parent = parent
177
178
179    def createEditor(self, parent, option, index):
180        editor = QLineEdit(parent)
181        v = DoubleValidator(editor, min=0.)
182        editor.setValidator(v)
183        return editor
184
185
186    def setEditorData(self, editor, index):
187        editor.setAutoFillBackground(True)
188        value = from_qvariant(index.model().data(index, Qt.DisplayRole), to_text_string)
189        editor.setText(value)
190
191
192    def setModelData(self, editor, model, index):
193        if not editor.isModified():
194            return
195        if editor.validator().state == QValidator.Acceptable:
196            value = from_qvariant(editor.text(), float)
197            for idx in self.parent.selectionModel().selectedIndexes():
198                if idx.column() == index.column():
199                    model.setData(idx, value, Qt.DisplayRole)
200
201
202#-------------------------------------------------------------------------------
203# Line edit delegate for the value
204#-------------------------------------------------------------------------------
205
206class IterationDelegate(QItemDelegate):
207    def __init__(self, parent=None):
208        super(IterationDelegate, self).__init__(parent)
209        self.parent = parent
210
211
212    def createEditor(self, parent, option, index):
213        editor = QLineEdit(parent)
214        v = IntValidator(editor, min=1)
215        editor.setValidator(v)
216        return editor
217
218
219    def setEditorData(self, editor, index):
220        editor.setAutoFillBackground(True)
221        value = from_qvariant(index.model().data(index, Qt.DisplayRole), to_text_string)
222        editor.setText(value)
223
224
225    def setModelData(self, editor, model, index):
226        if not editor.isModified():
227            return
228        if editor.validator().state == QValidator.Acceptable:
229            value = from_qvariant(editor.text(), float)
230            for idx in self.parent.selectionModel().selectedIndexes():
231                if idx.column() == index.column():
232                    model.setData(idx, value, Qt.DisplayRole)
233
234
235#-------------------------------------------------------------------------------
236# Scheme class
237#-------------------------------------------------------------------------------
238
239class StandardItemModelScheme(QStandardItemModel):
240
241    def __init__(self, NPE, dicoM2V, dicoV2M):
242        """
243        """
244        QStandardItemModel.__init__(self)
245        self.mdl = NPE
246        self.dicoM2V  = dicoM2V
247        self.dicoV2M  = dicoV2M
248
249        self.headers = [self.tr("Name"),
250                        self.tr("Scheme"),
251                        self.tr("Slope\nTest")]
252
253        self.setColumnCount(len(self.headers))
254
255        self._data  = []
256
257
258    def data(self, index, role):
259        if not index.isValid():
260            return None
261
262        if role == Qt.ToolTipRole:
263            return None
264
265        elif role == Qt.DisplayRole:
266            data = self._data[index.row()][index.column()]
267            if data:
268                return data
269            else:
270                return None
271
272        elif role == Qt.CheckStateRole:
273            data = self._data[index.row()][index.column()]
274            if index.column() == 2:
275                if data == 'on':
276                    return Qt.Checked
277                else:
278                    return Qt.Unchecked
279
280        elif role == Qt.TextAlignmentRole:
281            return Qt.AlignCenter
282
283        return None
284
285
286    def flags(self, index):
287        if not index.isValid():
288            return Qt.ItemIsEnabled
289
290        elif index.column() == 0 :
291            return Qt.ItemIsEnabled | Qt.ItemIsSelectable
292        elif index.column() == 2:
293            data = self._data[index.row()][1]
294            if data == 'upwind':
295                return Qt.ItemIsSelectable
296            else:
297                return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
298        else:
299            return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
300
301
302    def headerData(self, section, orientation, role):
303        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
304            return self.headers[section]
305        return None
306
307
308    def setData(self, index, value, role):
309        if not index.isValid():
310            return Qt.ItemIsEnabled
311
312        # Update the row in the table
313        row = index.row()
314        col = index.column()
315        var = self._data[row][0]
316
317        # Scheme
318        if col == 1:
319            new_scheme = from_qvariant(value, to_text_string)
320            self._data[row][col] = new_scheme
321            self.mdl.setSchemeModel(var, self.dicoV2M[new_scheme])
322            self._data[row][2] = str(self.mdl.getSlopeTestStatus(var))
323
324        # Slope Test
325        elif col == 2:
326            state = from_qvariant(value, int)
327            if state == Qt.Unchecked:
328                self._data[row][col] = "off"
329                self.mdl.setSlopeTestStatus(var,"off")
330            else:
331                self._data[row][col] = "on"
332                self.mdl.setSlopeTestStatus(var,"on")
333
334        self.dataChanged.emit(index, index)
335        return True
336
337
338    def getData(self, index):
339        row = index.row()
340        return self._data[row]
341
342
343    def newItem(self, variable):
344        """
345        Add/load a variable in the model.
346        """
347        row = self.rowCount()
348
349        scheme = self.dicoM2V[self.mdl.getSchemeModel(variable)]
350        slope  = self.mdl.getSlopeTestStatus(variable)
351
352        var = [variable, scheme, slope]
353
354        self._data.append(var)
355        self.setRowCount(row+1)
356
357
358#-------------------------------------------------------------------------------
359# Solver class
360#-------------------------------------------------------------------------------
361
362class StandardItemModelSolver(QStandardItemModel):
363
364    def __init__(self, NPE, dicoM2V, dicoV2M):
365        """
366        """
367        QStandardItemModel.__init__(self)
368        self.mdl = NPE
369        self.dicoM2V  = dicoM2V
370        self.dicoV2M  = dicoV2M
371
372        self.headers = [self.tr("Name"),
373                        self.tr("Solver"),
374                        self.tr("Solver\nprecision"),
375                        self.tr("Maximum\niteration")]
376
377        self.setColumnCount(len(self.headers))
378
379        self._data  = []
380
381
382    def data(self, index, role):
383        if not index.isValid():
384            return None
385
386        if role == Qt.ToolTipRole:
387            return None
388
389        elif role == Qt.DisplayRole:
390            data = self._data[index.row()][index.column()]
391            if data:
392                return str(data)
393            else:
394                return None
395
396        elif role == Qt.TextAlignmentRole:
397            return Qt.AlignCenter
398
399        return None
400
401
402    def flags(self, index):
403        if not index.isValid():
404            return Qt.ItemIsEnabled
405
406        if index.column() == 0 :
407            return Qt.ItemIsEnabled | Qt.ItemIsSelectable
408        else:
409            return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
410
411
412    def headerData(self, section, orientation, role):
413        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
414            return self.headers[section]
415        return None
416
417
418    def setData(self, index, value, role):
419        if not index.isValid():
420            return Qt.ItemIsEnabled
421
422        # Update the row in the table
423        row = index.row()
424        col = index.column()
425        var = self._data[row][0]
426
427        # Solver
428        if col == 1:
429            new_solver = from_qvariant(value, to_text_string)
430            self._data[row][col] = new_solver
431            self.mdl.setSolverModel(var, self.dicoV2M[new_solver])
432
433        # Precision solver
434        elif col == 2:
435            coeff  = from_qvariant(value, float)
436            self._data[row][col] = coeff
437            self.mdl.setSolverPrecision(var, coeff)
438
439        # Maximum iteration
440        elif col == 3:
441            coeff  = from_qvariant(value, int)
442            self._data[row][col] = coeff
443            self.mdl.setMaximumIteration(var, coeff)
444
445        self.dataChanged.emit(index, index)
446        return True
447
448
449    def getData(self, index):
450        row = index.row()
451        return self._data[row]
452
453
454    def newItem(self, variable):
455        """
456        Add/load a variable in the model.
457        """
458        row = self.rowCount()
459
460        solver = self.dicoM2V[self.mdl.getSolverModel(variable)]
461        prec   = self.mdl.getSolverPrecision(variable)
462        iter   = self.mdl.getMaximumIteration(variable)
463
464        var = [variable, solver, prec, iter]
465
466        self._data.append(var)
467        self.setRowCount(row+1)
468
469
470#-------------------------------------------------------------------------------
471# Main class
472#-------------------------------------------------------------------------------
473
474class NumericalParamEquationView(QWidget, Ui_NumericalParamEquation):
475    """
476    """
477    def __init__(self, parent, case):
478        """
479        Constructor
480        """
481        QWidget.__init__(self, parent)
482        Ui_NumericalParamEquation.__init__(self)
483        self.setupUi(self)
484
485        self.case = case
486        self.case.undoStopGlobal()
487        self.mdl = NumericalParamEquatModel(self.case)
488
489        # dico
490        self.dicoM2V = {"automatic"                              : 'Automatic',
491                        "jacobi"                                 : 'Jacobi (neptune_cfd)',
492                        "pcg"                                    : 'PCG (neptune_cfd)',
493                        "cgstab"                                 : 'CGSTAB (neptune_cfd)',
494                        "jacobi_saturne"                         : 'Jacobi (code_saturne)',
495                        "pcg_saturne"                            : 'PCG (code_saturne)',
496                        "bicgstab_saturne"                       : 'BICGSTAB (code_saturne)',
497                        "bicgstab2_saturne"                      : 'BICGSTAB-2 (code_saturne)',
498                        "gmres_saturne"                          : 'GMRES (code_saturne)',
499                        "gauss_seidel_saturne"                   : 'Gauss-Seidel (code_saturne)',
500                        "sym_gauss_seidel_saturne"               : 'Sym. Gauss-Seidel (code_saturne)',
501                        "pcr3_saturne"                           : 'PCR3 (code_saturne)',
502                        "centered"                               : 'Centered',
503                        "upwind"                                 : 'Upwind',
504                        "solu"                                   : 'SOLU'}
505        self.dicoV2M = {"Automatic"                                    : 'automatic',
506                        "Jacobi (neptune_cfd)"                         : 'jacobi',
507                        "PCG (neptune_cfd)"                            : 'pcg',
508                        "CGSTAB (neptune_cfd)"                         : 'cgstab',
509                        "Jacobi (code_saturne)"                        : 'jacobi_saturne',
510                        "PCG (code_saturne)"                           : 'pcg_saturne',
511                        "BICGSTAB (code_saturne)"                      : 'bicgstab_saturne',
512                        "BICGSTAB-2 (code_saturne)"                    : 'bicgstab2_saturne',
513                        "GMRES (code_saturne)"                         : 'gmres_saturne',
514                        "Gauss-Seidel (code_saturne)"                  : 'gauss_seidel_saturne',
515                        "Sym. Gauss-Seidel (code_saturne)"             : 'sym_gauss_seidel_saturne',
516                        "PCR3 (code_saturne)"                          : 'pcr3_saturne',
517                        "Centered"                                     : 'centered',
518                        "Upwind"                                       : 'upwind',
519                        "SOLU"                                         : 'solu'}
520
521        # Scheme
522        self.modelScheme = StandardItemModelScheme(self.mdl, self.dicoM2V, self.dicoV2M)
523        self.tableViewScheme.setModel(self.modelScheme)
524        self.tableViewScheme.setAlternatingRowColors(True)
525        self.tableViewScheme.resizeColumnToContents(0)
526        self.tableViewScheme.resizeRowsToContents()
527        self.tableViewScheme.setSelectionBehavior(QAbstractItemView.SelectItems)
528        self.tableViewScheme.setSelectionMode(QAbstractItemView.ExtendedSelection)
529        self.tableViewScheme.setEditTriggers(QAbstractItemView.DoubleClicked)
530        if QT_API == "PYQT4":
531            self.tableViewScheme.horizontalHeader().setResizeMode(QHeaderView.Stretch)
532        elif QT_API == "PYQT5":
533            self.tableViewScheme.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
534
535        delegateScheme   = SchemeDelegate(self.tableViewScheme, self.dicoM2V, self.dicoV2M)
536
537        self.tableViewScheme.setItemDelegateForColumn(1, delegateScheme)
538
539        # Solveur
540        self.modelSolver = StandardItemModelSolver(self.mdl, self.dicoM2V, self.dicoV2M)
541        self.tableViewSolver.setModel(self.modelSolver)
542        self.tableViewSolver.setAlternatingRowColors(True)
543        self.tableViewSolver.resizeColumnToContents(0)
544        self.tableViewSolver.resizeRowsToContents()
545        self.tableViewSolver.setSelectionBehavior(QAbstractItemView.SelectItems)
546        self.tableViewSolver.setSelectionMode(QAbstractItemView.ExtendedSelection)
547        self.tableViewSolver.setEditTriggers(QAbstractItemView.DoubleClicked)
548        if QT_API == "PYQT4":
549            self.tableViewSolver.horizontalHeader().setResizeMode(QHeaderView.Stretch)
550        elif QT_API == "PYQT5":
551            self.tableViewSolver.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
552
553        delegateSolver    = SolverDelegate(self.tableViewSolver, self.dicoM2V, self.dicoV2M)
554        delegatePrecision = PrecisionDelegate(self.tableViewSolver)
555        delegateIteration = IterationDelegate(self.tableViewSolver)
556        self.tableViewSolver.setItemDelegateForColumn(1, delegateSolver)
557        self.tableViewSolver.setItemDelegateForColumn(2, delegatePrecision)
558        self.tableViewSolver.setItemDelegateForColumn(3, delegateIteration)
559        if QT_API == "PYQT4":
560            self.tableViewSolver.horizontalHeader().setResizeMode(QHeaderView.Stretch)
561        elif QT_API == "PYQT5":
562            self.tableViewSolver.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
563
564        # Connect signals to slots
565        self.modelSolver.dataChanged.connect(self.dataChangedSolver)
566        self.modelScheme.dataChanged.connect(self.dataChangedScheme)
567        self.tabWidgetScheme.currentChanged[int].connect(self.slotchanged)
568
569        # load variables
570        for var in self.mdl.getVariableList() :
571            self.modelSolver.newItem(var)
572            self.modelScheme.newItem(var)
573        self.tableViewSolver.resizeRowsToContents()
574
575        self.tabWidgetScheme.setCurrentIndex(self.case['current_tab'])
576
577        self.case.undoStartGlobal()
578
579
580    def dataChangedSolver(self, topLeft, bottomRight):
581        for row in range(topLeft.row(), bottomRight.row()+1):
582            self.tableViewSolver.resizeRowToContents(row)
583        for col in range(topLeft.column(), bottomRight.column()+1):
584            self.tableViewSolver.resizeColumnToContents(col)
585
586
587    def dataChangedScheme(self, topLeft, bottomRight):
588        for row in range(topLeft.row(), bottomRight.row()+1):
589            self.tableViewScheme.resizeRowToContents(row)
590        for col in range(topLeft.column(), bottomRight.column()+1):
591            self.tableViewScheme.resizeColumnToContents(col)
592
593
594    @pyqtSlot(int)
595    def slotchanged(self, index):
596        """
597        Changed tab
598        """
599        self.case['current_tab'] = index
600