#!/usr/bin/env python
# -*- coding: utf-8 -*-

## @file
## @todo Linux executables created with cx_freeze report sys.stdout.encoding equal to None. To avoid program crash when trying to write non-ascii characters, these characters are replaced by '?'. It would be better to enforce writting uft-8 to stdout regardless of sys.stdout.encoding . 
## @todo Possibility to change styles with a combobox. Not obvious in pyQt.
##@todo Check end-of-line in a Mac computer. It should work if platform.system() returns something different than 'Windows',  'Microsoft',  'Vista' or 'Linux'.

u"""@package mwfiltersgui Graphical User Interface
<p>@author J.M. Rius, J. Mateu, J.M. Tamayo, C. Collado, A. Padilla and J. O'Callaghan. 
<p>Dpt. Signal Tehory and Communications, Universitat Politècnica de Catalunya (UPC), 
@version 2.0.3
@date June 11, 2013
<p>Acknowledgement: The software was developed in the frame of contract 21398/08/NL/GLC with the European Space Agency (ESA). Technical Offer was Christoph Ernst. 
Further features were developed under contract UPC-C7767 with Thales Alenia Space España (TAS-E). 
Contributions to the definition of the software functionality and testing have been made by Christoph Ernst, Mónica Martínez Mendoza and other 
ESA-ESTEC personnel, and Santiago Sobrino and Luis Roglá from TAS-E.
<p>Copyright: &copy; 2009 Universitat Politècnica de Catalunya (UPC). <br>Contact: lossyfilters@tsc.upc.edu
<p>This program is free software; you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation; either 
version 3 of the License, or (at your option) any later version.
<p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
<p>You should have received a copy of the GNU General Public License along with this program 
in file "LICENSE.GPL3";  if not, download it from http://www.gnu.org/licenses/gpl-3.0.html  .
<p>Additional permission under GNU GPL version 3 section 7:
<p>If you modify this Program, or any covered work, by linking or combining it with the "Extra Filters Library" (libextrafilters), 
the "Lossy Filters Library" (liblossyfilters) or the "License Check Library" (libchecklicense), or modified versions of that libraries, 
containing parts covered by the terms described in files LICENSE.LIBEXTRAFILTERS and LICENSE.LIBLOSSYFILTERS, 
the licensors of this Program grant you additional permission to convey the resulting work. 
<p>Lossyfilters software GUI uses the open-source dbplot.py module, which is based in part on the work of the Qwt project 
(http://qwt.sf.net) and has been released by UPC under the terms of the GNU GPL version 3.
""" 

import sys
import getopt
import os.path
import platform
import codecs
import types
import copy
from math import log10,  ceil,  pi,  sin,  sqrt
from functools import partial
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import PyQt4.Qwt5 as Qwt
from dbplot import DbPlot
import numpy as np
import scipy as sc
from scipy import interpolate
import Ui_mainwindow
import Ui_parameditdlg
import Ui_couplingmatrixdlg
import Ui_matrixeditdlg
import Ui_tempvariationdlg

##
# Global variable useful only for Mac OS X
MAC = "qt_mac_set_native_menubar" in dir()

from libcommonfunc import complexStr, listStr, myPrint, setGlobalVariablesLibCommonFunc, Parameters, readParamFile, printHeader, Sparameters, CouplingMatrices, FrequencyTransformBP, parseError, synthError, licenseError, criticalErrorMsg, warningMsg, saveSignificantDigits, saveSignificantDigitsEnergy
from libfreefilters import CharPolys,  setGlobalVariablesLibFreeFilters #, importedExtraFilters

# Try to import the libextrafilters. If it is not available, set importedExtraFilters flag to False.
try:
    if os.path.exists('libextrafilters.pyc'): existingExtraFilters = True
    else: existingExtraFilters = False
    from libextrafilters import TZoptimMask
except ImportError: 
    ##
    # Variable set to True if the non-free libextrafilters is available. Otherwise set to False.
    importedExtraFilters = False     
else: 
    importedExtraFilters = True

# Try to import the liblossyfilters. If it is not available, set importedLossyFilters flag to False.
try:
    if os.path.exists('liblossyfilters.pyc'): existingLossyFilters = True
    else: existingLossyFilters = False
    from liblossyfilters import  predistortion,  nonUniformQ,  importedCheckLicense,  existingCheckLicense
except ImportError: 
    ##
    # Variable set to True if the non-free liblossyfilters is available. Otherwise set to False.
    importedLossyFilters = False     
else: 
    importedLossyFilters = True    
    
    
class EnergyDbPlot(DbPlot):
    """
    Class derived from DbPlot to allow adding some widgets for plotting energy and power.
    """
#    def __init__(self, XData, leftYData,  rightXData = None, rightYData = None,  leftWidth = None,  rightWidth = None,  leftYmin = None, leftYmax = None, rightYmin = None, rightYmax = None, Xmin = None, Xmax = None, windowTitle = None,  tabTitle = None,  title=None,  leftYNames=None,  rightYNames=None,  leftYVisible=None,  rightYVisible=None,  XLabel=None,  leftYLabel=None,  rightYLabel=None,  leftYColors=None,  rightYColors=None, textLabel=None, Xunits=None,  leftYunits=None,  rightYunits = None,  parent=None):
    def __init__(self, *posPars, **keyPars):
        """
        Derived class constructor. 
        """
        super(EnergyDbPlot, self).__init__(*posPars, **keyPars)
        
        horLayout = QHBoxLayout()
        horLayout.addStretch()
        horLayout.addWidget(QLabel("Excitation:"))
        self.excitation_comboBox = QComboBox()
        self.excitation_comboBox.addItems(['Direct (S->L)', 'Reverse (L->S)'])
        horLayout.addWidget(self.excitation_comboBox)
        horLayout.addSpacing(20)
        horLayout.addWidget(QLabel("Input power:"))
        self.inputPower_doubleSpinBox = QDoubleSpinBox()
        self.inputPower_doubleSpinBox.setRange(0, 1e9)
        self.inputPower_doubleSpinBox.setValue(1)
        horLayout.addWidget(self.inputPower_doubleSpinBox)
        horLayout.addWidget(QLabel("W"))
        
#        horLayout.addStretch()
#        horLayout.addWidget(QLabel("Input power:"))
#        self.inputPower_lineEdit = QLineEdit('1')
#        realPositiveValidator = QRegExpValidator( reRealPositive,  self)
#        self.inputPower_lineEdit.setValidator(realPositiveValidator)
#        horLayout.addWidget(self.inputPower_lineEdit)
#        horLayout.addWidget(QLabel("W"))
        
        self.verticalLayout.insertLayout(0, horLayout)

        # Connect inputPower and excitation widgets to slot
        self.connect(self.inputPower_doubleSpinBox, SIGNAL("valueChanged(double)"), self.updateInputPower)
        self.connect(self.excitation_comboBox, SIGNAL("currentIndexChanged(int)"), self.updateExcitation)
        
        ##
        # List of leftYData for reverse excitation. List for all Tabs.
        self.leftYDataRev = []
        
        ##
        # List of rightYData for reverse excitation. List for all Tabs.
        self.rightYDataRev = []
    
    
    def updateInputPower(self,  value):
        """
        Update energy and power plots after inputPower value has been changed by the user.
        Y-axis are autoscaled after update.
        """
        
        for nTab in range(self.Ntabs):
            newLeftYData = [ value*data for data in np.array(self.leftYData[nTab]) ]
            newRightYData =  [ value*data for data in self.rightYData[nTab] ]if self.rightYData[nTab] is not None else None
               
            self.update(nTab, XData = self.XData[nTab], rightXData = self.rightXData[nTab], leftYData = newLeftYData, rightYData = newRightYData )
            self.autoScaleSomeAxis(nTab, autoScaleBottomX = False, autoScaleLeftY = True, autoScaleRightY = True)
            
            
    def updateExcitation(self, value):
        """
        Update energy and power plots after excitation value has been changed by the user.
        Y-axis are autoscaled after update.
        """
        
        for nTab in range(self.Ntabs):
            if value == 0:     
                newLeftYData = self.leftYData[nTab]
                newRightYData = self.rightYData[nTab]
            elif value == 1: 
                newLeftYData = self.leftYDataRev[nTab]
                newRightYData = self.rightYDataRev[nTab]
            
            self.update(nTab, XData = self.XData[nTab], rightXData = self.rightXData[nTab], leftYData = newLeftYData, rightYData = newRightYData )
            self.autoScaleSomeAxis(nTab, autoScaleBottomX = False, autoScaleLeftY = True, autoScaleRightY = True)
            
            
    def appendReverseData(self, leftYDataRev, rightYDataRev):
        """
        Set YData for reverse excitation. 
        This method appends data to a list for all tabs, so it must be called for all tabs sequentially in tab order.
        @param leftYDataRev = Left-y data for reverse excitation.
        @param rightYDataRev = Right-y data for reverse excitation.
        """
        self.leftYDataRev.append(leftYDataRev)
        self.rightYDataRev.append(rightYDataRev)
            
            
    def replaceReverseData(self, nTab, leftYDataRev, rightYDataRev):
        """
        Replace YData for reverse excitation. 
        @param nTab = Tab index.
        @param leftYDataRev = Left-y data for reverse excitation.
        @param rightYDataRev = Right-y data for reverse excitation.
        """
        self.leftYDataRev[nTab] = leftYDataRev
        self.rightYDataRev[nTab] = rightYDataRev
        
        # Show the direct or reverse data depending on the excitation widget status
        self.updateExcitation(self.excitation_comboBox.currentIndex())
        
        
class TempVarDlg(QDialog, Ui_tempvariationdlg.Ui_TempVarDlg):
    """
    Widget to ask the user for temperature variation parameters.
    """
    
    def __init__(self, parent=None):
        super(TempVarDlg, self).__init__(parent)
#        self.setAttribute(Qt.WA_DeleteOnClose) # Do not set Qt.WA_DeleteOnClose in modal dump dialogs: you would not be able to read the data after accepting the dialog
        self.setupUi(self)
        

class SpecMask(object):
    """
    Specification mask class.
    """
    
    def __init__(self,  fileName,  parent=None):
        """
        Read specification mask from file.
        @param fileName = File Name
        @param parent = Parent window for the temperature variation dialog
        """
        
        # Start helper functions
        
        def getTempVar():
            """
            Open a widget to get temperature variation parameters:
            Tamb = Ambient Temperature: Temperature at which the filter is built. At ambient temperature, the measured [S] parameters correspond to the designed [S] parameters.
            Tmax = Maximum operating temperature. Due to thermal expansion, the filter at Tmax is actually larger than the filter built at Tamb and the [S] parameters are shifted to the left (to lower frequencies).
            Tmin = Minimum operating temperature. Due to thermal expansion, the filter at Tmin is actually smaller than the filter built at Tamb and the [S] parameters are shifted to the right (to higher frequencies).
            alpha = Thermal expansion coefficient, expressed in parts per million per degree Celsius. Example: 22 ppm/C for alumminium.
            @return freqShiftRight = Relative frequency shift of the mask to the right = alpha*(Tmax-Tamb)*1e-6.
            @return freqShiftLeft  = Relative frequency shift of the mask to the left = alpha*(Tamb-Tmin)*1e-6.
            """
            temVarDlg = TempVarDlg(parent)
            if temVarDlg.exec_():
                if temVarDlg.temVar_groupBox.isChecked() == True:
                    alpha = temVarDlg.coef_doubleSpinBox.value()*1e-6
                    Tamb = temVarDlg.Tamb_doubleSpinBox.value()
                    Tmax = temVarDlg.Tmax_doubleSpinBox.value()
                    Tmin = temVarDlg.Tmin_doubleSpinBox.value()
                    return alpha*(Tmax-Tamb), alpha*(Tamb-Tmin)
                else:
                    return 0, 0
            else:
                return None, None
        
            
        def nextNonComment():
            """
            Helper function that returns next item that does not start with '!' of iterLines iterator.
            Items returned by iterLines iterator must be strings.
            @return line = First item that does not start with '!'
            """
            line = file.readline()
            while len(line)>0 and (line.startswith('!') or len(line.strip()) == 0): line = file.readline()
            return line
        
        def getUnitsFactor(units):
            """
            Helper function that returns the conversion factor corresponding to 'units' argument.
            @param  units = Units. Must be a key in the unitsFactorDict dictionary. String.
            @return factor = Frequency conversion factor
            """
            try: factor = unitsFactorDict[units]
            except KeyError,  err: raise parseError,  "Incorrect frequency units '%s', should be one of: %s" % (units,  str(unitsFactorDict.keys()))
            return factor
            
        def getData(line):
            """
            Helper function that returns the data read form specification mask file.
            The units conversion factors are applied.
            @param line = String to process (line of specification mask file).
            @return data = Numpy array containing the data.
            @return line = String containing next non-comment line of specification mask file after the data, normally starting with '#'.
            """
            # Get unit factors
            unitsFactors = np.array([ getUnitsFactor(units) for units in  unitsList ] )
            
            data = []
            while len(line)>0 and not line.startswith('#'): # Check len(line)>0 to detect EOF
            
                # Get data and check dimensions
                xArray = np.array( [ float(x) for x in line.split() ] )
                if len(xArray) != len(unitsFactors): raise parseError,  "Number of data columns different than number of units in Mask format line (%s)" % str([parType] + unitsList)
                data.append(xArray * unitsFactors)
                
                #Get next line
                line = nextNonComment()
            data = np.array(data).squeeze()
            
            if data.ndim == 0: data = float(data)   # An scalar
                
            return (data,  line)
            
        # End helper functions
        
        fbasename = os.path.basename(fileName)
        
        ##
        # File name
        self.fileName = fbasename
        
        ##
        # Parent window
        self.mainWindow = parent
        
        ##
        # Central frequency
        self.f0 = None
        
        ##
        # Bandwidth
        self.BW = None
        
        ##
        # Specification for S11 (return losses) in dB. Float.
        self.S11spec = None
        
        ##
        # Value of S21 at f0
        self.S21_F0 = None
        
        ##
        # Value of S21 over bandwidth
        self.S21_BW = None
        
        ##
        # Specification for S21 variation over bandwidth (insertion losses) in dB pk-pk. 
        # Numpy float array Nx2 where N is the number of points, frequency data (Hz) is in the 1st column and Mask data in the 2nd.
        self.S21inbandSpecDelta = None
        
        ##
        # Specification for S21 out of band rejection in dB.
        # Numpy float array Nx2 where N is the number of points, frequency data (Hz) is in the 1st column and Mask data in the 2nd.
        self.S21outbandSpec = None
        
        ##
        # Group delay mask (sec).
        # Numpy float array Nx2 where N is the number of points, frequency data (Hz) is in the 1st column and Mask data in the 2nd.
        self.GDspec = None
        
        ##
        # Frequency samples in the pass band for optimization
        self.freqInbandOptim = None
        
        ##
        # Frequency samples in the rejection band for optimization
        self.freqOutBandOptim = None
        
        ##
        # Array with all frequency samples for optimization, in Hz, the first self.NfreqInBandOptim correspond to self.freqInbandOptim and the rest ot self.freqOutBandOptim
        self.evalFoptim = None
        
        ##
        # Array with all frequency samples for optimization, in normalized s-plane, the first self.NfreqInBandOptim correspond to self.freqInbandOptim and the rest ot self.freqOutBandOptim
        self.evalSoptim = None
        
        ##
        # Number of samples in self.evalSoptim corresponding to the pass band = len(self.freqInbandOptim)
        self.NfreqInBandOptim = None
        
        ##
        # S11 mask samples in the pass band for optimization
        self.S11optim =  None
        
        ##
        # S21 mask samples in the pass band for optimization
        self.S21inBandOptim = None 
        
        ##
        # S21 mask samples in the rejection band for optimization
        self.S21outBandOptim = None 
        
        
        try:
            file = open(fileName,  'r')
        except IOError,  err:
            criticalErrorMsg( 'Cannot open Mask specification file:<p>' + unicode(err) )
            myPrint('Failed to open Mask specification file: %s' % fbasename)
            return

        freqShiftRight,  freqShiftLeft = getTempVar()
        if freqShiftLeft is None and freqShiftRight is None:
            freqShiftLeft, freqShiftRight = 0, 0    # Cancelling or closing the dialog is equal to no temperature variation
        myPrint('Mask temperature freq shift (relative) = %.2e to the right and %.2e to the left' % (freqShiftRight,  freqShiftLeft))
        
        ##
        # Factor to multiply the frequencies that shift to the right
        self.freqFactorRight = 1+freqShiftRight
        
        ##
        # Factor to multiply the frequencies that shift to the left
        self.freqFactorLeft = 1-freqShiftLeft

        myPrint('Reading [S] Mask specification file: %s' % fbasename)
        
        unitsFactorDict = { 'Hz': 1,  'kHz': 1e3,  'MHz': 1e6,  'GHz': 1e9,  'dB': 1,  's': 1, 'ms': 1e-3, 'us': 1e-6,  'ns': 1e-9 }
        
        try:
            line = nextNonComment()
            while len(line) > 0:  # while not EOF
                if not line.lstrip().startswith('#'): raise parseError, 'Incorrect file format'
                optionList = line[1:].split()       # Leading '#' removed
                parType,  unitsList = optionList[0],  optionList[1:]
                line = nextNonComment()
                
                if    parType.upper() == 'f0'.upper():                       self.f0,  line = getData(line)
                elif parType.upper() == 'BW'.upper():                       self.BW,  line =  getData(line)
                elif parType.upper() == 'S11_BW'.upper():                self.S11spec,  line =  getData(line)
                elif parType.upper() == 'S21_f0'.upper():                self.S21_F0,  line =  getData(line)
                elif parType.upper() == 'S21_BW'.upper():                self.S21_BW,  line =  getData(line)
                elif parType.upper() == 'S21_f_inband'.upper():      self.S21inbandSpecDelta,  line = getData(line)
                elif parType.upper() == 'GD_f'.upper():                    self.GDspec,  line = getData(line)
                elif parType.upper() == 'S21_f_outband'.upper():    self.S21outbandSpec,  line = getData(line)
            file.close()
    
        except (parseError,  ValueError),  err:
            criticalErrorMsg( 'ERROR parsing specifications mask file:<p>' + unicode(err) )
            myPrint('Failed to read file: %s'  % fbasename)
        else:
            myPrint('Loaded file: %s' % fbasename)
            
        
    def specToMask(self, SP):
        """
        Process specification data to obtain the mask curves.
        The mask values are computed from specification relative values adding the relevant [S] parameter data.
        @param SP = Sparameters class instance
        """
        
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        # BEGIN: Helper function
        
        def applyFreqShift(mask, type):
            """
            Apply frequency shift to mask.
            @param mask = Mask numpy array
            @param type = 'outer' or 'inner'. 
            'outer' masks expand: the right freq shift is applied to the left portion of the data and the left frequency shift to the right portion.
            'inner' masks collapse: the left freq shift is applied to the left portion of the data and the right frequency shift to the right portion.
            """
            if mask is not None:
                M = mask.shape[0]
                
                assert type in ['outer',  'inner']
                if type == 'outer':
                    (leftPartfactor, rightPartFactor) = (self.freqFactorRight, self.freqFactorLeft)
                elif type == 'inner':
                    (leftPartfactor, rightPartFactor) = (self.freqFactorLeft, self.freqFactorRight)
                    
                # Apply factors. Works for M even or odd using integer division (5/2=2). 
                # Use int() just in case future versions of python implement floating point division.
                mask[:int(M/2), 0] *=        leftPartfactor
                mask[int((M+1)/2):, 0] *= rightPartFactor

        # END: Helper function
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        
        ##
        # Mask for S11 (return losses) as a function of frequency. Numpy array.
        self.S11mask = None
        
        if self.S11spec is not None:
            self.S11mask = np.array([ (self.f0 - self.BW/2,  -self.S11spec),  (self.f0 + self.BW/2,  -self.S11spec) ])

        ##
        # Mask for S21 inband (insertion losses)
        self.S21inBandMask = None

        maxS21 = max( 20*np.log10(np.abs(SP.S21)) )
        
        if self.S21_BW is not None and self.S21_F0 is not None:
            S21inband = np.array([ (self.f0 - self.BW/2,  -self.S21_BW),  (self.f0,  -self.S21_F0),  (self.f0 + self.BW/2,  -self.S21_BW) ])
        else:  S21inband = np.array([[],[]]).T
        
        if self.S21inbandSpecDelta is not None:
            S11variation = self.dataSymmetrizeDeltaF0(self.S21inbandSpecDelta,  0)
            S11variation[:, 1] = maxS21 - S11variation[:, 1]
        else: S11variation = np.array([[],[]]).T
            
        self.S21inBandMask = np.concatenate(( S21inband, S11variation ) )
        if len(self.S21inBandMask) > 0: self.freqSort(self.S21inBandMask, 0)
        else: self.S21inBandMask = None
        
        ##
        # Mask for out-of-band rejection as a function of frequency. Numpy array.
        self.S21outBandMask = None
        
        if self.S21outbandSpec is not None:
            self.S21outBandMask = self.dataSymmetrizeDeltaF0(self.S21outbandSpec,  0,  hideBW = True)
            self.S21outBandMask[:, 1] = maxS21 - self.S21outBandMask[:, 1]
        
        ##
        # Mask for group delay (upper).
        self.GDmaskUpper = None
        
        ##
        # Mask for group delay (lower).
        self.GDmaskLower = None        
        
        if self.GDspec is not None:
            # Group delay at the centre frequency (average of GD between f0-BW/4 and f0+BW/4)
            indBW = (SP.freq < self.f0 + self.BW/4 ) &  (SP.freq > self.f0 - self.BW/4)
            GDf0  = np.mean(SP.groupDelay[indBW])
            
            self.GDmaskUpper = self.dataSymmetrizeDeltaF0(self.GDspec,  0)
            self.GDmaskUpper[:, 1] = GDf0 + self.GDmaskUpper[:, 1]
            
            self.GDmaskLower = self.dataSymmetrizeDeltaF0(self.GDspec,  0)
            self.GDmaskLower[:, 1] = GDf0 - self.GDmaskLower[:, 1]
            
            
        # Apply mask shift with temperature
        for maskAndType in [ (self.S21outBandMask, 'outer'), (self.S11mask, 'inner'), (self.S21inBandMask, 'inner'), (self.GDmaskUpper, 'inner'), (self.GDmaskLower, 'inner') ]: 
            applyFreqShift(*maskAndType)
        
#        print self
        
    
    def maskSamplesOptimization(self, FT, Nsamples):
        """
        Create mask samples to be used as a reference in optimization.
        Half the samples go to the passband and half to the rejection band (symmetrically).
        The frequency samples for optimization are obtained by linear interpolation of the mask data.
        Only the magnitude of [S] is optimized to comply with the S11 and S21 masks.
        Group delay in the mask file, if any, is not used in optimization, 
        
        @param FT = FrequencyTransform class instance, containing f0 and BW parameters.
        @param Nsamples = Total number of samples. Half go to the passband and half to the rejection band (symmetrically).
        """
        
        if self.S11mask is None: 
            raise synthError, "For optimization with a reference mask, it is necessary to<br>define 'S11_BW' optional parameter in the mask file."
            
        if self.S21outBandMask is None: 
            raise synthError, "For optimization with a reference mask, it is necessary to<br>define 'S21_f_outband' optional parameter in the mask file."
        
        if self.S21_F0 is None or self.S21_BW is None:
            raise synthError, "For optimization with a reference mask, it is necessary to<br>define 'S21_F0' and 'S21_BW' optional parameters in the mask file."
            
        assert self.S21inBandMask is not None        # Cannot be None if self.S21_F0 and self.S21_BW are not None
        
        # Epsilon to guarantee that optimization frequency samples are within the mask interval, with posible roundoff errorsº
        eps = self.f0/1e12
        
        # Frequency sampling points in the pass band
        NpassBand = int(Nsamples/2)
        minFreqInBand =  np.min( np.concatenate([ self.S11mask[:, 0], self.S21inBandMask[:, 0], self.GDmaskUpper[:, 0], self.GDmaskLower[:, 0] ]) ) + eps
        maxFreqInBand = np.max( np.concatenate([ self.S11mask[:, 0], self.S21inBandMask[:, 0], self.GDmaskUpper[:, 0], self.GDmaskLower[:, 0] ]) ) - eps
        self.freqInbandOptim = np.linspace(minFreqInBand, maxFreqInBand, NpassBand)
        
        # Frequency sampling points in the rejection band
        # We have to remove the NaN samples from self.S21outBandMask, 
        NS21out = len(self.S21outBandMask)  
        assert NS21out % 2 == 0, 'Number of samples in S21outBandMask must be even' # Number of out band mask samples is always even, since the mask is symmetric
        nonNaN = range(0, NS21out/2-1) + range(NS21out/2+1, NS21out)   # Indices on no-NaN samples
        
        minFreqOutBandLower = np.min(self.S21outBandMask[:NS21out/2, 0]) + eps
        maxFreqOutBandLower = np.max(self.S21outBandMask[:NS21out/2, 0]) - eps
        minFreqOutBandUpper = np.min(self.S21outBandMask[1+NS21out/2:, 0]) +eps 
        maxFreqOutBandUpper = np.max(self.S21outBandMask[1+NS21out/2:, 0]) - eps
        
        NoutBand   = int((Nsamples+1)/2)
        freqOutBandLower = np.linspace(minFreqOutBandLower, maxFreqOutBandLower, int(NoutBand/2) )
        freqOutBandUpper = np.linspace(minFreqOutBandUpper, maxFreqOutBandUpper, int((NoutBand+1)/2) )
        self.freqOutBandOptim = np.concatenate([ freqOutBandLower, freqOutBandUpper ])
        
        # Convenience data to save computation in the cost function
        self.evalFoptim = np.concatenate([ self.freqInbandOptim, self.freqOutBandOptim ])   # Necessary for BW optimization: the FT will change with BW
        self.evalSoptim = 1j * FT.normFreq( self.evalFoptim )
        self.NfreqInBandOptim = len(self.freqInbandOptim)
        
        # Interpolating functions
        interpS11              = interpolate.interp1d(self.S11mask[:, 0], self.S11mask[:, 1], bounds_error = False)
        interpS21inBand   = interpolate.interp1d(self.S21inBandMask[:, 0], self.S21inBandMask[:, 1], bounds_error = False)
        interpS21outBand = interpolate.interp1d(self.S21outBandMask[nonNaN, 0], self.S21outBandMask[nonNaN, 1], bounds_error = False)
        
        # Interpolated masks
        self.S11optim = interpS11(self.freqInbandOptim)
        self.S21inBandOptim = interpS21inBand(self.freqInbandOptim)
        self.S21outBandOptim = interpS21outBand(self.freqOutBandOptim)
        
    
    def dataSymmetrizeDeltaF0(self,  inArray,  freqCol,  hideBW = False):        
        """
        Sort and symmetrise data with respect to centre frequency self.f0 and add self.f0 to frequency column of array.
        Frequencies in the input array are delta with respect to centre frequency self.f0.
        Frequencies in the result are absolute.
        @param inArray = Input numpy array.
        @param freqCol = Column index of frequency values.
        @param hideBW = Flag to hide the centre of the mask. If True, the mask is not displayed at the centre, between the symmetric data points. Default False.
        @return outArray = Output numpy array
        """
        array1 = inArray.copy()
        array2 = array1.copy()
        array1[:, freqCol] = self.f0 - array1[:, freqCol]
        array2[:, freqCol] = self.f0 + array2[:, freqCol]
        
        if hideBW: 
            closestFreq = min(inArray[:, 0])    # Minimum delta freq, which corresponds to the freq closest to f0
            hideCenter = np.array([ [self.f0 - closestFreq, np.nan], [self.f0 + closestFreq, np.nan]  ])
            outArray = np.concatenate((array1,  hideCenter, array2))
        else:
            outArray = np.concatenate((array1, array2))
            
        self.freqSort(outArray, freqCol)
        return outArray
        
        
    def freqSort(self,  inArray,  freqCol):
        """
        Sort array by frequency (inplace).
        The input array is modified inplace
        @param inArray = Input numpy array.
        @param freqCol = Column index of frequency values.
        """
        sortedIndices = inArray[:,freqCol].argsort()
        inArray[:, :] = inArray[sortedIndices, :]
        
        
    def __str__(self):
        st = ''
        st += '*** Specifications ***\n\n'
        st += 'Central frequency:\n' + str(self.f0) + '\n\n'
        st += 'Bandwidth:\n' + str(self.BW) + '\n\n'
        st += 'Specification for S11 (return losses):\n' + str(self.S11spec) + '\n\n'
        st += 'Value of S21 at f0 (insertion losses):\n' + str(self.S21_F0) + '\n\n'
        st += 'Value of S21 over bandwidth (insertion losses):\n' + str(self.S21_BW) + '\n\n'
        st += 'S21 variation over bandwidth (insertion losses):\n' + str(self.S21inbandSpecDelta) + '\n\n'
        st += 'S21 out of band rejection: \n' + str(self.S21outbandSpec) + '\n\n'
        st += 'Group delay mask:\n' + str(self.GDspec) + '\n\n'
        
        st += '*** Masks ***\n\n'
        st += 'Mask for S11 (return losses): \n' + str(self.S11mask) + '\n\n'
        st += 'Mask for S21 in-band (insertion losses)\n' + str(self.S21inBandMask) + '\n\n'
        st += 'Mask for S21 for out-of-band rejection: \n' + str(self.S21outBandMask) + '\n\n'
        st += 'Mask for GD (upper): \n' + str(self.GDmaskUpper) + '\n\n'
        st += 'Mask for GD (lower): \n' + str(self.GDmaskLower) + '\n\n'
        
        return st
        
    
class Stouchstone(object):
    """
    [S] parameters class, as read from Touchstone file.
    It contains the minimum number of attributes to plot the [S] parameters in the results comparison window.
    Of course, the attributes share the same name as the corresponding ones in libcommonfunc.Sparameters class.
    """
    
    def __init__(self, fileName):
        """
        Set frequency axis and S parameters, including phase and group delay, read from a Touchstone file. 
        Touchstone is a trademark of Agilent Corporation 
        @param fileName = File Name        
        """
        
        fbasename = os.path.basename(fileName)
        
        try:
            file = open(fileName,  'r')
        except IOError,  err:
            criticalErrorMsg( 'Cannot open [S] Touchstone file:<p>' + unicode(err) )
            myPrint('Failed to open [S] Touchstone file: %s' % fbasename)
            return

        myPrint('Reading [S] from Touchstone file: %s' % fbasename)
        
        freqFactor = 1e9
        dataFormat = 'MA'
        freqfactorDict = { 'HZ': 1,  'KHZ': 1e3,  'MHZ': 1e6,  'GHZ': 1e9}
        optionsLoaded = False
        freq,  S11,  S21,  S12,  S22 = [],  [],  [],  [],  []
        Sread = False
        
        try:
            stList = file.readlines()
            for st in stList:
                if len(st.lstrip()) == 0: continue    # Empty line
                if st.lstrip().startswith('!'): 
                    if Sread: break  # If S parameters have been already read, this comment line will start noise specifications, and we want to quit here.
                    else: continue    # A comment
                
                if st.lstrip().startswith('#'):     # The options string
                    if optionsLoaded: raise parseError,  "There are more than one options line."
                        
                    optionList = st.split()
                    
                    # Check number of options
                    if len(optionList) != 6: raise parseError,  "Incorrect number of items in option line."
                    
                    # Parse frequency units
                    units = optionList[1].upper()
                    try: freqFactor = freqfactorDict[units]
                    except KeyError,  err: raise parseError,  "Incorrect frequency units '%s'" % units
                    
                    # Parse type of parameter
                    type = optionList[2]
                    if type != 'S': raise parseError,  "Type of parameter is not 'S'."
                    
                    # Parse data format
                    dataFormat = optionList[3].upper()
                    if dataFormat not in ['MA',  'DB',  'RI']: raise parseError,  "Data format '%s' is not 'MA', 'DB', or 'RI'." % dataFormat
                    
                    # Parse reference impedance
                    if optionList[4] != 'R': raise parseError,  "Options line does not include 'R' parameter."
                    impedance = optionList[5]
                    
                    optionsLoaded = True
                
                else:
                    try: f, S11a,  S11b,  S21a,  S21b,  S12a,  S12b,  S22a,  S22b  = [ float(n) for n in st.split()] 
                    except ValueError: raise parseError,  "Data line '%s' contains incorrect number of items" % st
                    
                    freq.append(f * freqFactor)
                    
                    if dataFormat == 'RI':
                        S11.append( complex(S11a, S11b) ) 
                        S21.append( complex(S21a, S21b) ) 
                        S12.append( complex(S12a, S12b) )
                        S22.append( complex(S22a, S22b) )
                    if dataFormat == 'MA':
                        S11.append( S11a * np.exp(1j * S11b * pi/180) )
                        S21.append( S21a * np.exp(1j * S21b * pi/180) )
                        S12.append( S12a * np.exp(1j * S12b * pi/180) )
                        S22.append( S22a * np.exp(1j * S22b * pi/180) )
                    if dataFormat == 'DB':
                        S11.append( 10**(S11a/20) * np.exp(1j * S11b * pi/180) )
                        S21.append( 10**(S21a/20) * np.exp(1j * S21b * pi/180) )
                        S12.append( 10**(S12a/20) * np.exp(1j * S12b * pi/180) )
                        S22.append( 10**(S22a/20) * np.exp(1j * S22b * pi/180) )
                    Sread = True
              
            file.close()
    
        except parseError,  err:
            criticalErrorMsg( 'ERROR parsing [S] Touchstone file:<p>' + unicode(err) )
            myPrint('Failed to read file: %s'  % fbasename)
        else:
            myPrint('Loaded file: %s' % fbasename)
            
        ##
        # Frequency axis (Hz)
        self.freq = np.array(freq)
        
        ##
        # Parameter \f$ S_{11} \f$
        self.S11 = np.array(S11)
        
        ##
        # Parameter \f$ S_{12} \f$
        self.S21 = np.array(S21)
        
        ##
        # Parameter \f$ S_{21} \f$
        self.S12 = np.array(S12)
        
        ##
        # Parameter \f$ S_{22} \f$
        self.S22 = np.array(S22)
        
        ##
        # Phase of return losses (rad)
        self.phaseS11 = np.unwrap(np.angle(self.S11))
        
        ##
        # Phase of inverse transfer function (rad)
        self.phaseS12 = np.unwrap(np.angle(self.S12))
        
        ##
        # Phase of transfer function (rad)
        self.phaseS21 = np.unwrap(np.angle(self.S21))
        
        ##
        # Phase of inverse return losses (rad)
        self.phaseS22 = np.unwrap(np.angle(self.S22))
        
        ##
        # Frequency axis with sampling points in the middle of self.freq sampling.
        # It is necessary to plot self.groupDelay2
        self.freq2 = (self.freq[:-1] + self.freq[1:])/2
        
        ##
        # Group delay for the unnormalized filter (sec), sampled at self.freq2
        self.groupDelay2 = -np.diff(self.phaseS21) / (2*pi* np.diff(self.freq))

        # Set causality principle. There may be spurious negative peaks due to computing phase at transmission zeros.
        self.groupDelay2[ self.groupDelay2<0 ] = 0
       

class myTableWidget(QTableWidget):
    """
    Subclass for:
    - reimplementation of SinzeHint and MinimumSizeHint
    - Methods for tables of transmission zeros
    """
    
    def __init__(self, name = None,  mainDlg = None, parent=None):
        super(myTableWidget,  self).__init__(parent)
        
        ##
        # Table name.
        self.name = name
        
        ##
        # Main dialog to which this table belongs, used only where applicable.
        self.mainDlg = mainDlg
        
        ##
        # Frequency units, used only where applicable.
        self.units = None
        
        ##
        # Filter center frequency.
        # It will be used for computation of 'f-f0' column and for autoupdate of 'Imag' and 'f-f0' colums when one of them is edited.
        self.f0 = None       
        
        ##
        # Flag to inform that changes in table cells are due to user interaction
        self.updatedByUser = True
        

    def setUnits(self,  units):
        """ Store units in self.untis, to use in TZ table headers.
        @param units = Units string.
        """
        self.units = units
    
    
    def setf0(self, updateImag = True):
        """
        Set the value in Center Frequency widget as self.f0.
        It will be used for computation of 'f-f0' column and for autoupdate of 'Imag' and 'f-f0' colums when one of them is edited.
        @param updateImag: If updateImag is True, then the 'Imag' column of the whole table is updated according to f0 and to the f-f0 column.
                           If updateImag is False, f0 is only stored in self.f0 and the 'Imag' column is not updated.
        """
        self.f0 = float(self.mainDlg.f0_lineEdit.text()) * 10**(3*self.mainDlg.f0UnitsIndex)
        
        if updateImag: # Update TZ Imag column according to f0 and values of f-f0 column
            
            M = self.rowCount()
            freqFactor = pow(10,  3*self.mainDlg.transmissionZerosUnitsIndex)
            
            for m in range(M):
                item = self.item(m, 2) 
                stIm = str(item.text()).strip() if item is not None else ''
                
                if stIm == '':
                    newItem = QTableWidgetItem('')
                    self.setItem(m, 1, newItem)
                else: 
                    im = float(stIm) * freqFactor
                    newItem = QTableWidgetItem(  '%.*g' % (saveSignificantDigits, (im + self.f0) / freqFactor) )
                    newItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    self.setItem(m, 1, newItem)
        

    def updateTableSize(self):
        """
        Resize rows and columns to fit contents
        """
        self.resizeRowsToContents()
        self.resizeColumnsToContents()
        self.setMaximumSize( self.maximumSizeHint() )


    def tableCellChanged(self,  row,  col):
        """
        Table cell has been modified by user, take necessary actions.
        If Imag or Imag-f0 columns have been modified, updated the companion cell
        """

        if not self.updatedByUser: return   # Do nothing if it changes are not due to user
        
        # Avoid infinite recursion
        self.updatedByUser = False
        
        freqFactor = pow(10,  3*self.mainDlg.transmissionZerosUnitsIndex)
        
        self.setCurrentCell(row,  col)
        item =  self.currentItem()
        item.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)    # It may be an item automatically created with default alignment
        # Here item string is a valid float or an empty string '', since it has been filtered before
        stIm = str(item.text()).strip() if item is not None else '' 
        
        if stIm == '':  # If updated cell has nothing, put also nothing in the companion cell
            newItem = QTableWidgetItem('')
            if col == 1:    self.setItem(row, 2, newItem)
            elif col == 2: self.setItem(row, 1, newItem)
        else: 
            im = float(stIm) * freqFactor
            if col == 1:    # Imag has been changed
                newItem = QTableWidgetItem(  '%.*g' % (saveSignificantDigits, (im - self.f0) / freqFactor) )
                newItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                self.setItem(row, 2, newItem)
            elif col == 2: # f-f0 has been changed
                newItem = QTableWidgetItem(  '%.*g' % (saveSignificantDigits, (im + self.f0) / freqFactor) )
                newItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                self.setItem(row, 1, newItem)                 

        self.updateTableSize()
        self.mainDlg.updateTotaltransmissionZeros()
        
        self.updatedByUser = True
        

    def addRow(self):
        """
        Append new row for a new zero
        """
        
        N = self.rowCount()
        self.setRowCount(N+1)
        
        self.setCurrentCell(N, 1)
        self.resizeRowsToContents()
        self.resizeColumnsToContents()
        self.setMaximumSize( self.maximumSizeHint() )
        self.mainDlg.updateDlgHeight()
        

    def sizeHint(self):
        return maximumSizeHint()
        
    
    def maximumSizeHint(self):
        #size = super(myTableWidget,  self).sizeHint()
        size = QTableWidget.sizeHint(self)
        
        # Compute space for table with headers and ScrollBars (just in case they appear).
        # At this point, the size of the ScrollBar is given by  self.horizontalScrollBar().height(),
        # for both the horizontal and vertical ScrollBars. 
        # self.verticalScrollBar().width() contains and absurd vale here.
        width = self.verticalHeader().width() + self.horizontalScrollBar().height()
        for c in range(self.columnCount()): width += self.columnWidth( c )
        size.setWidth( width + 8 )
        
        height = self.horizontalHeader().height() + self.horizontalScrollBar().height()
        for r in range(self.rowCount()): height += self.rowHeight( r )
        size.setHeight( height + 4 )
        
#        if self.name is not None: print self.name
#        print 'HSB:',  self.horizontalScrollBar().isVisible(),  self.horizontalScrollBar().width(),  self.horizontalScrollBar().height()
#        print 'VSB:',  self.verticalScrollBar().isVisible(),  self.verticalScrollBar().width(),  self.verticalScrollBar().height()

        return size

##
#@todo Reimplment myTableWidget.resizeEvent() function to reduce size of table when there are no ScrollBars

#    def resizeEvent(self,  event):
#        ''' Reimplementation of the resize event to check state of the toolbars.
#        '''
#        if self.horizontalScrollBar().isVisible() or self.verticalScrollBar().isVisible():
#            maxSize = self.maximumSizeHint()
#            if self.horizontalScrollBar().isVisible(): maxSize.setHeight(maxSize.height() - self.horizontalScrollBar().height())
#            if self.verticalScrollBar().isVisible(): maxSize.setWidth(maxSize.height() - self.horizontalScrollBar().height())
#            self.setMaximumSize(maxSize)
#            
#        if self.name is not None: print self.name
#        print 'event new size:' ,  event.size().width(),  event.size().height()
#        print 'event old size:' ,  event.oldSize().width(),  event.oldSize().height()
#        print 'self:',  self.width(),  self.height()
#        print 'HSB:',  self.horizontalScrollBar().isVisible(),  self.horizontalScrollBar().width(),  self.horizontalScrollBar().height()
#        print 'VSB:',  self.verticalScrollBar().isVisible(),  self.verticalScrollBar().width(),  self.verticalScrollBar().height()
        

    def sizeHint(self):
        return self.maximumSizeHint()
    

class MainWindow(QMainWindow, Ui_mainwindow.Ui_MainWindow):
    """
    GUI main window class.
    """
    
    def __init__(self, fileName,  parent=None):
        """
        Constructor to initialize main window.
            @param fileName = Name of file to automatically open after creating the window. If equal to None, no file is open.
            @param parent = Parent widget, in this case is None.
        """
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        
        ##
        # Variable to store the modified or unmodifed status of parameters class instance P.
        self.dirty = False
        
        ##
        # Variable that will avoid cleanup if set to True.
        # It is used when a method that calls self.clenaup() is called by the GUI and not by the user.
       # The user may want to avoid cleanup in some situations. 
        self.noCleanup = False
        
        ##
        # Name of the parameters file to process.
        # When main Window is created, self.fileName is a copy of the file name given in the command line when calling the application.
        # Later self.fileName can be modified when opening a parameter file with the GUI.
        self.fileName = fileName
        
        ##
        #  Application name, to be accesible outside this module.
        self.applicationName = applicationName
        
        self.settings = QSettings()
        self.restoreGeometry(self.settings.value("Geometry").toByteArray())
        self.restoreState(self.settings.value("State").toByteArray())
        
        ##
        # Last open directory name
        self.lastDir = unicode(self.settings.value('LastDirectoryOpened',  QVariant('.')).toString())
        
        # Set window title , initially there is no modified file to save
        self.setWindowTitle(applicationName)
        self.setWindowModified(False)
        
        # Disable selections in log_listWidget
        self.log_listWidget.setSelectionMode(QAbstractItemView.NoSelection)
        
        # Add results selection comboBox to toolBar
        self.results_comboBox = QComboBox(self.toolBar_2)
        self.results_comboBox.setObjectName("results_comboBox")
        self.results_comboBox.setToolTip("Load result from list")
        self.results_comboBox.setStatusTip("Load result from list")
        self.results_comboBox.setWhatsThis("Load result from list")
        self.connect(self.results_comboBox,  SIGNAL("currentIndexChanged(int)"),  self.on_results_comboBox_currentIndexChanged)
        self.toolBar_2.insertWidget(self.action_ListRemove, self.results_comboBox)
        
        # Add what's this action
        self.toolBar_3.insertAction(self.action_FileExit,  QWhatsThis.createAction(self.toolBar_3))
        
        ##
        # Parameters class instance containing filter and synthesis parameters.
        # Read from the parameters file and -maybe- modified with the Parameter Edit Dialog.
        self.P = None
        
        ##
        # Copy of the CharPolys class instance containing characteristic polynomials.
        self.CP = None
        
        ##
        # Copy of the CouplingMatrices class instance containing coupling matrices.
        self.CM = None
        
        ##
        # Copy of the SParameters class instance containing [S] parameters.
        self.SP = None
        
        ##
        # Copy of the FrequencyTransform class instance containing the frequency transform.
        self.FT = None
        
        ##
        # Specification Mask class instance.
        self.SM = None
        
        ##
        # Flag that is True when a successful computation has been done.
        # When the flag is False, no results are plotted.
        self.computed = False
        
        ##
        # QMainWindow that contains the parameter edit window.
        self.paramEdit = None
        
        ##
        # QwtPlot that contains the Magnitude and Phase S-parameter plot.
        self.SPlotMagPhase = None
        
        ##
        # QwtPlot that compares S-parameters obtained from CP from those obtained from CM.
        self.SPlotError = None
        
        ##
        # QwtPlot that compares S-parameters for different results.
        self.SPlotCompare = None
        
        ##
        # QwtPlot that contains stored energy and dissipated power
        self.EnergyPlot = None
        
        ##
        # QwtPlot that contains S-parameters for sensitivity analysis
        self.SPlotSensitivity = None
        
        ##
        # QMainWindow that contains the coupling matrix table.
        self.CMdialog = None
        
        ##
        # List of results to compare
        self.listResults = [ ]
        
        ##
        # Last index of results selection comboBox
        self.indexResults = None
        
        ##
        # Flag that controls if frequency axis must be autoscaled. It will become true after changing the sweep min and max frequencies.
        self.autoScaleFreq = False
        
        ##
        # Store the mainWindow class instance into global variable 'mainWindow' defined in the libcommonfunc.py and libfreefilters.py
        # in order to let the console interface functions in libcommonfunc.py and libfreefilters.py access the GUI mainWindow.
        # The GUI methods access the mainWindow class through ParamEditDlg.mainWindow and MainWindow self attributes.
        setGlobalVariablesLibCommonFunc(mainWindow = self)
        setGlobalVariablesLibFreeFilters(mainWindow = self)
        
        # Print startup output in log window. Must import here the preGuiOutput variable, to catch its current value.
        from libcommonfunc import preGuiOutput
        myPrint(preGuiOutput)
        
        # Read file if there is one to read
        if self.fileName is not None: 
            self.fileRead(self.fileName)
            if autoRun: 
                self.action_Execute.trigger()            
                if autoMatrix: 
                    self.action_CouplingMatrix.trigger()            
                    if autoEditMatrix: 
                        self.CMdialog.action_Edit.setChecked(True)
        
        
    def okToContinue(self):
        """
        This function checks if there are unsaved changes and, if there are, aks the user to save them.
        The function is executed by the GUI when there is an action that will destroy the parameter class P: Close application, Open file, New file, etc.
            @return ok (True / False) = If True the use has agreed to continue, if False the user cancels the operation.
        """
        if self.dirty:
            if self.fileName is not None:
                message = "File %s has unsaved changes\nSave?" % os.path.basename(self.fileName)
            else:
                message = "New file with unsaved changes\nSave?"
            reply = QMessageBox.question(self, "%s - Unsaved Changes" % applicationName, message, QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
            if reply == QMessageBox.Cancel:
                return False
            elif reply == QMessageBox.Yes:
                self.on_action_FileSaveAs_triggered(False)
        return True
        
        
    def updateStatus(self, message):
        """
        Print message and update window title.
        Prints message in the log widget at the main window background and also at the status bar for 5 sec.
        Updates main window title with open file name and modified state.
            @param message = Message to print (string).
        """
        self.statusBar().showMessage(message, 5000)
        self.log_listWidget.addItem(message)
        self.log_listWidget.setCurrentRow(self.log_listWidget.count()-1)
        if self.fileName is not None:
            self.setWindowTitle("%s - %s[*]" % (applicationName ,  os.path.basename(self.fileName)) )
        elif self.P is not None and self.P.empty is not True:
            self.setWindowTitle("%s - Unnamed[*]" % applicationName)
        else:
            self.setWindowTitle("%s[*]" % applicationName)
        self.setWindowModified(self.dirty)
        
        
    def fileRead(self,  fname):
        """
        Reads parameters from a file.
            @param fname = File name.
        """
        # Initally set filename to None just in case the file parsing fails and the file is not read.
        self.fileName = None
        self.P = readParamFile(fname)
        fbasename = os.path.basename(fname)
        # Update self.lastDir
        self.lastDir = os.path.dirname(fname)
        
        if self.P is None:
            QMessageBox.warning(self,  "WARNING", 
                """<p align="center">It is not possible to open file %s<br>
            because it contains an invalid entry:</p>
            <p align="center">Please open the file with a text editor and fix it.
            </p>
         """ % fbasename )
            message = "Failed to read file: %s" % fbasename
        else:
            self.fileName = fname
            message = "Loaded file: %s" % fbasename
        self.updateStatus(message)
        
        # Create Frequency Transform class instance for this parameters
        if self.P is not None: 
            self.FT = FrequencyTransformBP(self.P)

        # Open parameter editor dialog
        if self.P is not None: 
            self.paramEdit = ParamEditDlg(self)
            self.paramEdit.show()
            self.paramEdit.raise_()
            #self.paramEdit.activateWindow()
            
            
    def closeSPlotMagPhaseWindow(self):
        """
        Close SPlotMagPhase plot window, if it exists.
        """
        # Close CouplingMatrix dialog if it exists
        if self.SPlotMagPhase is not None and not self.SPlotMagPhase.closed:
            self.SPlotMagPhase.close()
            self.SPlotMagPhase = None
    
    
    def closeResultsWindow(self):
        """
        Close SPlotCompare plot windows, if they exist.
        """       
        if self.SPlotCompare is not None and not self.SPlotCompare.closed:
            self.SPlotCompare.close()
            self.SPlotCompare = None
            
            
    def closeCMWindows(self):
        """
        Close Coupling Matrix window and CM error plot window, if they exist.
        """
        # Close CouplingMatrix dialog if it exists
        if self.CMdialog is not None: 
            self.CMdialog.close()
            self.CMdialog = None
            
        # Close [S] error plot if it exists
        if self.SPlotError is not None and not self.SPlotError.closed:
            self.SPlotError.close()
            self.SPlotError = None
            
        # Close energy plot if it exists
        if self.EnergyPlot is not None and not self.EnergyPlot.closed:
            self.EnergyPlot.close()
            self.EnergyPlot = None
            
            
    def cleanup(self):
        """
        Clean up previous results and any open plot or coupling matrix window.
        Clean up is disabled if self.nocleanup is True, in that case this method does nothing.
        Sets self.dirty to False.
        """
        if self.noCleanup is True: return
        
        self.closeSPlotMagPhaseWindow()
        self.closeCMWindows()
        (self.P,  self.CP,  self.CM,  self.SP,  self.FT) = (None,  None, None, None, None)
    
    
    def plotSParameters(self):
        """
        Plot [S] parameters magnitude and phase graph, if they are available.
        """
        
        if self.SP is not None:
            if  self.SPlotMagPhase is None or self.SPlotMagPhase.closed is True:
                
                # Set dynamic range to the lowest value of S21 at sweep range ends.
                linRange = min([ abs(self.SP.S21[0]), abs(self.SP.S21[-1] )])
                if linRange > 1e-10: dynamicRange = -20*log10(linRange) 
                else:                        dynamicRange = 200
                dynamicRange = 10*ceil(dynamicRange/10)
                
                self.SPlotMagPhase = DbPlot( parent=self,  
                                                                windowTitle = '[S] parameters', 
                                                                tabTitle = '[S] + Group delay', 
                                                                title = '[S] parameters (from Characteristic Polynomials)',                                                                     
                                                                XData = self.SP.freq / 1e9, 
                                                                leftYData = [20*np.log10(np.abs(self.SP.S11)),  20*np.log10(np.abs(self.SP.S21)),  20*np.log10(np.abs(self.SP.S22))], 
                                                                rightYData = [ 1e9 * self.SP.groupDelay ],
                                                                leftYmin = -dynamicRange, 
                                                                leftYmax = 0, 
                                                                leftYNames = ['S<sub>11</sub>',  'S<sub>21</sub>',  'S<sub>22</sub>'],  
                                                                leftYVisible = [True,  True,  False], 
                                                                XLabel = 'Frequency (GHz)', 
                                                                leftYLabel = 'dB', 
                                                                Xunits = 'GHz',  leftYunits = 'dB', rightYunits = 'ns', 
                                                                rightYLabel = 'Time (ns.)' , 
                                                                rightYNames = ['Group delay'], 
                                                                rightYVisible = [True]
                                                                )
                self.SPlotMagPhase.addTab(  tabTitle = '[S] + Phase', 
                                                                title = '[S] parameters (from Characteristic Polynomials)',                                                                     
                                                                XData = self.SP.freq / 1e9, 
                                                                leftYData = [20*np.log10(np.abs(self.SP.S11)),  20*np.log10(np.abs(self.SP.S21)),  20*np.log10(np.abs(self.SP.S22)) ], 
                                                                rightYData = [ self.SP.phaseS21 * 180/np.pi, self.SP.phaseS11 * 180/np.pi,  self.SP.phaseS22 * 180/np.pi, self.SP.deviationLP * 180/np.pi ],
                                                                leftYmin = -dynamicRange, 
                                                                leftYmax = 0, 
                                                                leftYNames = ['S<sub>11</sub>',  'S<sub>21</sub>',  'S<sub>22</sub>'], 
                                                                leftYVisible = [True,  True,  False], 
                                                                XLabel = 'Frequency (GHz)', 
                                                                leftYLabel = 'dB', 
                                                                Xunits = 'GHz',  leftYunits = 'dB', rightYunits = 'deg', 
                                                                rightYLabel = 'degrees' , 
                                                                rightYNames = ['Phase(S<sub>21</sub>)',  'Phase(S<sub>11</sub>)',  'Phase(S<sub>22</sub>)',  'Deviation S<sub>21</sub> from <br>linear phase'], 
                                                                rightYVisible = [True,  False,  False,  False]
                                                                )
                self.SPlotMagPhase.addTab(  tabTitle = 'Group delay + Phase', 
                                                                title = '[S] parameters (from Characteristic Polynomials)',                                                                     
                                                                XData = self.SP.freq / 1e9, 
                                                                leftYData = [1e9 * self.SP.groupDelay], 
                                                                rightYData = [ self.SP.phaseS21 * 180/np.pi,  self.SP.deviationLP * 180/np.pi ],
                                                                leftYNames = ['Group delay'], 
                                                                XLabel = 'Frequency (GHz)', 
                                                                leftYLabel = 'Time (ns.)', 
                                                                Xunits = 'GHz',  leftYunits = 'ns', rightYunits = 'deg',
                                                                rightYLabel = 'degrees' , 
                                                                rightYNames = ['Phase(S<sub>21</sub>)',  'Deviation from <br>linear phase'], 
                                                                rightYVisible = [True,  False]
                                                                )
                
            else:
                self.SPlotMagPhase.update(0,  self.SP.freq / 1e9,  [20*np.log10(np.abs(self.SP.S11)),  20*np.log10(np.abs(self.SP.S21)),  20*np.log10(np.abs(self.SP.S22))],  
                                                                        rightYData=[ 1e9 * self.SP.groupDelay] , autoScaleBottomX = self.autoScaleFreq)
                
                self.SPlotMagPhase.update(1,  self.SP.freq / 1e9,  [20*np.log10(np.abs(self.SP.S11)),  20*np.log10(np.abs(self.SP.S21)),  20*np.log10(np.abs(self.SP.S22))],   
                                                                        rightYData=[ self.SP.phaseS21 * 180/np.pi,  self.SP.phaseS11 * 180/np.pi,  self.SP.phaseS22 * 180/np.pi, self.SP.deviationLP * 180/np.pi ], 
                                                                       autoScaleBottomX = self.autoScaleFreq )
                
                self.SPlotMagPhase.update(2,  self.SP.freq / 1e9,  [1e9 * self.SP.groupDelay],   rightYData=[ self.SP.phaseS21 * 180/np.pi,  self.SP.deviationLP * 180/np.pi ], 
                                                                        autoScaleBottomX = self.autoScaleFreq )
                
                self.autoScaleFreq = False
            
            # Plot masks, or update if there are any
            if self.SM is not None:
                self.SPlotMagPhase.deleteMasks()
                self.plotMasks()                
        

    def plotMasks(self):
        """
        Add masks to [S] plots.
        """
        if self.SP is None: return      # If self.SP does not exist, there are no results to plot yet.
        
        self.SM.specToMask(self.SP)
        
        maskNameRL = 'Return losses\nmask'
        maskNameRJ = 'Rejection\nmask'
        maskNameIL = 'Insertion losses\nmask'
        maskNameGDL = 'Group delay\nlower mask'
        maskNameGDU = 'Group delay\nupper mask'
        
        if self.SPlotMagPhase is not None and self.SPlotMagPhase.closed is False and self.SPlotMagPhase.masks[0] == []: # Do not add masks if there are already
            if self.SM.S11mask is not None:
                self.SPlotMagPhase.addMask(nTab = 0,  XData = self.SM.S11mask[:, 0]/1e9, YData = self.SM.S11mask[:, 1], name = maskNameRL,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 32 )
                self.SPlotMagPhase.addMask(nTab = 1,  XData = self.SM.S11mask[:, 0]/1e9, YData = self.SM.S11mask[:, 1], name = maskNameRL,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 32 )
            
            if self.SM.S21outBandMask is not None:
                self.SPlotMagPhase.addMask(nTab = 0, XData = self.SM.S21outBandMask[:, 0]/1e9, YData =self.SM.S21outBandMask[:, 1], name = maskNameRJ,  color = Qt.red,  type = 'Upper', axis = 'left',  transpar = 32 )
                self.SPlotMagPhase.addMask(nTab = 1, XData = self.SM.S21outBandMask[:, 0]/1e9, YData =self.SM.S21outBandMask[:, 1], name = maskNameRJ,  color = Qt.red,  type = 'Upper', axis = 'left',  transpar = 32 )
                
            if self.SM.S21inBandMask is not None:
                self.SPlotMagPhase.addMask(nTab = 0, XData = self.SM.S21inBandMask[:, 0]/1e9, YData =self.SM.S21inBandMask[:, 1], name = maskNameIL,  color = Qt.red,  type = 'Lower', axis = 'left',  transpar = 32 )
                self.SPlotMagPhase.addMask(nTab = 1, XData = self.SM.S21inBandMask[:, 0]/1e9, YData =self.SM.S21inBandMask[:, 1], name = maskNameIL,  color = Qt.red,  type = 'Lower', axis = 'left',  transpar = 32 )
                
            if self.SM.GDmaskLower is not None:
                self.SPlotMagPhase.addMask(nTab = 2, XData = self.SM.GDmaskLower[:, 0]/1e9, YData =self.SM.GDmaskLower[:, 1]*1e9, name = maskNameGDL,  color = Qt.blue,  type = 'Lower', axis = 'left',  transpar = 64 )
                self.SPlotMagPhase.addMask(nTab = 2, XData = self.SM.GDmaskUpper[:, 0]/1e9, YData =self.SM.GDmaskUpper[:, 1]*1e9, name = maskNameGDU,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 64 )
            
            self.SPlotMagPhase.changeCurveVisibility(0,  [True,  False,  False],  [False], { maskNameRL: True, maskNameRJ: False, maskNameIL: False } )
            self.SPlotMagPhase.changeCurveVisibility(1,  [False, True, False], [False,  False, False,  False], { maskNameRL: False, maskNameRJ: True, maskNameIL: True} )
            self.SPlotMagPhase.changeCurveVisibility(2,  [True],  [False,  False], { maskNameGDL: True, maskNameGDU: True } )
                
        if self.SPlotError is not None and self.SPlotError.closed is False and self.SPlotError.masks[0] == []:  # Do not add masks if there are already
            if self.SM.S11mask is not None:                
                self.SPlotError.addMask(nTab = 0,  XData = self.SM.S11mask[:, 0]/1e9, YData = self.SM.S11mask[:, 1], name = maskNameRL,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 64 )
                
            if self.SM.S21outBandMask is not None:
                self.SPlotError.addMask(nTab = 1, XData = self.SM.S21outBandMask[:, 0]/1e9, YData =self.SM.S21outBandMask[:, 1], name = maskNameRJ,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 64 )
                
            if self.SM.S21inBandMask is not None:
                self.SPlotError.addMask(nTab = 1, XData = self.SM.S21inBandMask[:, 0]/1e9, YData =self.SM.S21inBandMask[:, 1], name = maskNameIL,  color = Qt.blue,  type = 'Lower', axis = 'left',  transpar = 64 )
                
            if self.SM.GDmaskLower is not None:
                self.SPlotError.addMask(nTab = 2, XData = self.SM.GDmaskLower[:, 0]/1e9, YData =self.SM.GDmaskLower[:, 1]*1e9, name = maskNameGDL,  color = QColor(0,  175,  0),  type = 'Lower', axis = 'left',  transpar = 64 )
                self.SPlotError.addMask(nTab = 2, XData = self.SM.GDmaskUpper[:, 0]/1e9, YData =self.SM.GDmaskUpper[:, 1]*1e9, name = maskNameGDU,  color = QColor(0,  175,  0),  type = 'Upper', axis = 'left',  transpar = 64 )
                
        if self.SPlotSensitivity is not None and self.SPlotSensitivity.closed is False and self.SPlotSensitivity.masks[0] == []: # Do not add masks if there are already
            if self.SM.S11mask is not None:
                self.SPlotSensitivity.addMask(nTab = 0,  XData = self.SM.S11mask[:, 0]/1e9, YData = self.SM.S11mask[:, 1], name = maskNameRL,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 32 )
                self.SPlotSensitivity.addMask(nTab = 1,  XData = self.SM.S11mask[:, 0]/1e9, YData = self.SM.S11mask[:, 1], name = maskNameRL,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 32 )
            
            if self.SM.S21outBandMask is not None:
                self.SPlotSensitivity.addMask(nTab = 0, XData = self.SM.S21outBandMask[:, 0]/1e9, YData =self.SM.S21outBandMask[:, 1], name = maskNameRJ,  color = Qt.red,  type = 'Upper', axis = 'left',  transpar = 32 )
                self.SPlotSensitivity.addMask(nTab = 1, XData = self.SM.S21outBandMask[:, 0]/1e9, YData =self.SM.S21outBandMask[:, 1], name = maskNameRJ,  color = Qt.red,  type = 'Upper', axis = 'left',  transpar = 32 )
                
            if self.SM.S21inBandMask is not None:
                self.SPlotSensitivity.addMask(nTab = 0, XData = self.SM.S21inBandMask[:, 0]/1e9, YData =self.SM.S21inBandMask[:, 1], name = maskNameIL,  color = Qt.red,  type = 'Lower', axis = 'left',  transpar = 32 )
                self.SPlotSensitivity.addMask(nTab = 1, XData = self.SM.S21inBandMask[:, 0]/1e9, YData =self.SM.S21inBandMask[:, 1], name = maskNameIL,  color = Qt.red,  type = 'Lower', axis = 'left',  transpar = 32 )
                
            if self.SM.GDmaskLower is not None:
                self.SPlotSensitivity.addMask(nTab = 2, XData = self.SM.GDmaskLower[:, 0]/1e9, YData =self.SM.GDmaskLower[:, 1]*1e9, name = maskNameGDL,  color = Qt.blue,  type = 'Lower', axis = 'left',  transpar = 64 )
                self.SPlotSensitivity.addMask(nTab = 2, XData = self.SM.GDmaskUpper[:, 0]/1e9, YData =self.SM.GDmaskUpper[:, 1]*1e9, name = maskNameGDU,  color = Qt.blue,  type = 'Upper', axis = 'left',  transpar = 64 )
            
            self.SPlotSensitivity.changeCurveVisibility(0,  [True,  False,  False],  [False], { maskNameRL: True, maskNameRJ: False, maskNameIL: False } )
            self.SPlotSensitivity.changeCurveVisibility(1,  [False, True, False], [False,  False, False], { maskNameRL: False, maskNameRJ: True, maskNameIL: True} )
            self.SPlotSensitivity.changeCurveVisibility(2,  [True],  [False], { maskNameGDL: True, maskNameGDU: True } )
            
    
    def updateWindows(self):
        """
        Update all plot windows and CM window if they exist.
        """
        SP = self.SP
        
        if SP is None:
            self.closeSPlotMagPhaseWindow()
            self.closeCMWindows()
        else:
            if self.SPlotMagPhase is not None and not self.SPlotMagPhase.closed:
                self.plotSParameters()
            
            if self.SPlotError is not None and not self.SPlotError.closed:
                self.CMdialog.on_action_PlotS_triggered(True)

        if self.CMdialog is not None: 
            self.CMdialog.close()
            self.CMdialog = CouplingMatrixDlg(self.CM,  self)
            self.CMdialog.show()
  
            if self.EnergyPlot is not None and not self.EnergyPlot.closed:   
                self.CMdialog.on_action_PlotEnergy_triggered(True) 
            
            if self.SPlotSensitivity is not None and not self.SPlotSensitivity.closed: 
                self.CMdialog.on_runSensitivity_pushButton_clicked(True)
            
        if self.paramEdit is not None:
            self.paramEdit.P = self.P
            self.paramEdit.updateDialogParams()


    def addResultToList(self,  resultArg):
        name = ''
        while len(name ) == 0:
            input = QInputDialog.getText (self , 'Result name', 'Give a name to current result:')
            if input[1] is False: 
                return
            name = input[0]
            if len(name) == 0:
                QMessageBox.warning(self, "WARNING",  'Result name cannot be an empty string.')
            
        # BE CAREFUL: Add new elements to the tuple AT THE END.
        # Elements will be accessed later as SP = result[3]
        result = copy.deepcopy( resultArg )
        
        index = self.results_comboBox.findText(name,  Qt.MatchExactly)

        if index >= 0:  # name exists in list
            message = "Result with same name already exists in list\nReplace?"
            reply = QMessageBox.question(self, "%s - Result exists" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.No:  # Exit without doing anything
                return False
            elif reply == QMessageBox.Yes:
                self.listResults[index] = result
                myPrint("Result '%s' replaced in list" % name)
        else:   # Append result to list
            self.listResults.append(result)
            self.noCleanup = True
            self.results_comboBox.addItem(name) # Will call self.on_action_ListRemove_triggered, which calls self.cleanup()
            index = self.results_comboBox.count() - 1
            self.results_comboBox.setCurrentIndex(index) # Will call self.on_action_ListRemove_triggered, which calls self.cleanup()
            self.noCleanup = False
            self.indexResults = index
            myPrint("Result '%s' appended to list" % name)


    ##@{
    # @name Functions automatically excuted when the user interacts with the GUI 

    def closeEvent(self,  event):
        """
        Reimplementation of the window close function.
        """
        # Check if there are unsaved changes and ask user what to do.
        if self.okToContinue():
            # Save window geometry and state 
            self.settings = QSettings()
            self.settings.setValue("Geometry",  QVariant(self.saveGeometry()))
            self.settings.setValue("State", QVariant(self.saveState()))
            # Save last open directory name
            self.settings.setValue('LastDirectoryOpened',  QVariant(self.lastDir))
            
            # Accept event -> close window
            event.accept()
        else:
            # Ignore event -> do not close window
            event.ignore()
        

    @pyqtSignature("bool")
    def on_action_HelpAbout_triggered(self,  checked):
        """
        Show "HelpAbout"" dialog from help menu.
        This function is automatically executed by the GUI when the user selects the menu Help/About.
        The information shown is read from the file docstring.
        """
        st =  __doc__
        st = st.replace('@package',  'Package')
        st = st.replace('@author',  'Authors:')
        st = st.replace('@date',  'Date:')
        st = st.replace('@version',  'Version:')

        st = u"""<b>Lossy Filters Synthesis</b> 
        <p>%s
        <p>Python %s - Qt %s - PyQt %s - Numpy %s - Scipy %s - QWT %s on %s %s""" % (st,  platform.python_version(),QT_VERSION_STR, PYQT_VERSION_STR, 
                                                                                     np.version.version, sc.version.version, Qwt.QWT_VERSION_STR, platform.system(),  platform.release()) 
        
        QMessageBox.about(self, "About %s" % applicationName, st)
        
    
    @pyqtSignature("bool")    
    def on_action_HelpHelp_triggered(self):
        """
        Show "HelpHelp"" dialog from help menu.
        This function is automatically executed by the GUI when the user selects the menu Help/Help.
        The information shown is read file Help/GUI_help.html.
        """
        form = HelpForm("GUI_help.html", self)
        form.show()        
        #QMessageBox.about(self, "Help %s" % applicationName, "Hello, no help for you")
        
        
    @pyqtSignature("bool")
    def on_action_FileExit_triggered(self,  checked):
        """
        Close application.
        This function is automatically executed by the GUI when the user triggers the FileExit action.
        """
        self.close()
    
    
    @pyqtSignature("bool")
    def on_action_Execute_triggered(self,  checked):
        """
        Execute synthesis computation.
        This function is automatically executed by the GUI when the user triggers the Execute action.
        """
        
        # Update self.P with values in the parameter editor, if it is open
        if self.paramEdit is not None and self.paramEdit.readDialogParams() == False: 
            return      # Do not compute with invalid parameters. The error message will be displayed by self.paramEdit.readDialogParams()
        
        if self.P is not None and self.P.empty is False:
            self.updateStatus("\nFilter synthesis started")
            app.setOverrideCursor(Qt.WaitCursor)
            self.CP,  self.CM,  self.SP,  self.computed = runSynthesis(self.P,  self.FT,  symmetrizeZeros)
            app.restoreOverrideCursor()
            
            if self.computed:
                self.updateStatus("Filter synthesis finished\n")
                
                # Plot [S] parameters
                self.plotSParameters()
                
                # Reload coupling matrix window, if it was open.
                if self.CMdialog is not None: 
                    self.CMdialog.close()
                    self.CMdialog = CouplingMatrixDlg(self.CM,  self)
                    self.CMdialog.show()
                
                # Update SPlotError, if it was open.
                if self.SPlotError is not None and not self.SPlotError.closed:
                    if self.CMdialog is not None:
                        self.CMdialog.on_action_PlotS_triggered(True)
                    else:
                        self.SPlotError.close()
  
                # Update EnergyPlot, if it was open.
                if self.EnergyPlot is not None and not self.EnergyPlot.closed:   
                    if self.CMdialog is not None:
                        self.CMdialog.on_action_PlotEnergy_triggered(True) 
                    else:
                        self.EnergyPlot.close()
            else:
                self.updateStatus("Filter synthesis aborted")
                
        else: 
            self.updateStatus("There are no filter parameters loaded")
            QMessageBox.warning(self, "WARNING",  'There are no filter parameters loaded. Open a parameter file or create a new one.')
            
        if self.paramEdit is not None:
            # Reload parameters into GUI widgets, they may have been modified by the computation
            self.paramEdit.updateDialogParams(autoFreqUnits = False)
            self.paramEdit.recomputed = True
        
        
    @pyqtSignature("bool")
    def on_action_CouplingMatrix_triggered(self,  checked):
        """
        Opens dialog to display and rotate coupling matrices.
        This function is automatically executed by the GUI when the user triggers the 'Coupling Matrix' action.
        """
        # Open  CouplingMatrix dialog
        if self.CM is not None:
            if self.CMdialog is None: self.CMdialog = CouplingMatrixDlg(self.CM,  self)
            self.CMdialog.show()
        else:
            QMessageBox.warning(self, "WARNING",  'No available coupling matrix to display. Open a parameter file and run synthesis.')
     
     
    @pyqtSignature("bool")
    def on_action_Edit_triggered(self,  checked):
        """
        Opens dialog to edit parameters.
        This function is automatically executed by the GUI when the user triggers the Edit action.
        """
        if self.paramEdit is not None:
            QMessageBox.warning(self, "WARNING",  'There is an open Parameter Edit Window. Close it before openning a new one.')
        else:
            # Open parameter editor dialog
            if self.P is not None: 
                self.paramEdit = ParamEditDlg(self)
                self.paramEdit.show()
                self.paramEdit.raise_()
                #self.paramEdit.activateWindow()
            else:
                QMessageBox.warning(self, "WARNING",  'No open parameter file to edit. Open a parameter file or create a new one.')
            
            
    @pyqtSignature("bool")
    def on_action_FileSave_triggered(self,  checked):
        """
        Save parameters to file.
        This function is automatically executed by the GUI when the user triggers the FileSave action.
        """
        if self.P is not None:  # Check if there are parameters to save
            if self.fileName is not None:   # Check if there is a filename
                # Open parameter file and write P on it. Use codecs module for transparent utf-8 encoding.
                fout = codecs.open(self.fileName, 'w',  'utf-8')
                fout.write( unicode(self.P) )
                fout.close()
                self.dirty = False
                self.setWindowModified(self.dirty)
                self.updateStatus("Parameters saved to file: %s" % self.fileName)
            else:
                self.on_action_FileSaveAs_triggered(False)
        else:
            QMessageBox.warning(self, "WARNING",  "There are no parameters to save.")
            
    @pyqtSignature("bool")
    def on_action_FileSaveAs_triggered(self,  checked):
        """
        Ask for a file name and save parameters to file.
        This function is automatically executed by the GUI when the user triggers the FileSaveAs action.
        """
        if self.P is not None:  # Check if there are parameters to save
            
            # Set save directory to directory of current file
            dir = os.path.dirname(self.fileName) if self.fileName is not None else "."
            
            # Ask for file name
            formats = ["*.par" ]
            fname = unicode(QFileDialog.getSaveFileName(self, "%s - Save Parameter File" % applicationName, dir, "Parameter files (%s)" % " ".join(formats)))
            
            # If fname is valid, save parameters. Otherwise (File Save dialog cancelled by user) do nothing
            if fname:
                fbasename = os.path.basename(fname)
                if "." not in fbasename:
                    fname += '.par'
                self.fileName = fname
                self.on_action_FileSave_triggered(False)
        else:
            QMessageBox.warning(self, "WARNING",  "There are no parameters to save.")
  
    @pyqtSignature("bool")
    def on_action_FileOpen_triggered(self,  checked):
        """
        Ask for a file name and load parameters from this file.
        This function is automatically executed by the GUI when the user triggers the FileOpen action.
        """
        if self.paramEdit is not None:
            QMessageBox.warning(self, "WARNING",  'There is an open Parameter Edit Window. Close it before openning a new one.')
        else:
            # Check if there are unsaved changes
            if not self.okToContinue():    
                return
            
            # Cleaup results and plots
            self.cleanup()
            
            # Set open directory to directory of current file or to lastDir if there is no open file
            dir = os.path.dirname(self.fileName) if self.fileName is not None else self.lastDir
            
            # Open file
            formats = ["*.par *.dat" ]
            fname = unicode(QFileDialog.getOpenFileName(self, "%s - Open Parameter File" % applicationName, dir, "Parameter files (%s)" % " ".join(formats)))
                        
            # If there is a file to open, read parameters. Otherwise (File Open dialog cancelled by user) do nothing
            if fname: self.fileRead(fname)
            
            
    @pyqtSignature("bool")
    def on_action_Mask_triggered(self,  checked):
        """
        If checked: Ask for a file name and load specification mask from this file. Plot the mask.
        If unchecked: Delete the mask data from memory and delete the mask curve from the plots.
        This function is automatically executed by the GUI when the user triggers the Mask action.
        """            
        if checked:
            # Set open directory to directory of current .par file or to lastDir if there is no open file
            dir = os.path.dirname(self.fileName) if self.fileName is not None else self.lastDir
            
            # Open file
            formats = ["*.msk" ]
            fname = unicode(QFileDialog.getOpenFileName(self, "%s - Open Mask Specification File" % applicationName, dir, "Mask spec files (%s)" % " ".join(formats)))
            
            if fname:
                self.action_Mask.setChecked(True)  # Necessary when this method is called by the program, not the user
                self.SM = SpecMask(fname,  self)

                # Plot mask if there is an open [S] plot window
                self.plotMasks()
                
            else:   # Unckeck toolbar button
                self.action_Mask.setChecked(False)
                

        else: 
            self.action_Mask.setChecked(False)  # Necessary when this method is called by the program, not the user
            self.SM = None
            
            # Remove existing masks
            if  self.SPlotMagPhase is not None and self.SPlotMagPhase.closed is False:
                self.SPlotMagPhase.deleteMasks()
                    
            if self.SPlotError is not None and self.SPlotError.closed is False: 
                self.SPlotError.deleteMasks()
                    
            if  self.SPlotSensitivity is not None and self.SPlotSensitivity.closed is False:
                self.SPlotSensitivity.deleteMasks()
                    
            # Restore default curve visibility
            if  self.SPlotMagPhase is not None and self.SPlotMagPhase.closed is False:
                self.SPlotMagPhase.changeCurveVisibility(0,  [True,  True,  False],  [True])
                self.SPlotMagPhase.changeCurveVisibility(1,  [True, True, False],  [True,  False, False,  False])
                self.SPlotMagPhase.changeCurveVisibility(2,  [True],  [True,  False])
                
            if  self.SPlotSensitivity is not None and self.SPlotSensitivity.closed is False:
                self.SPlotSensitivity.changeCurveVisibility(0,  [True,  True,  False],  [True])
                self.SPlotSensitivity.changeCurveVisibility(1,  [True, True, False],  [True,  False, False])
                self.SPlotSensitivity.changeCurveVisibility(2,  [True],  [True])
        
            
    @pyqtSignature("bool")
    def on_action_Sopen_triggered(self,  checked):
        """
        Ask for a file name and load [S] from this file in Touchstone format. Plot the [S] parameters in the results comparison window.
        This function is automatically executed by the GUI when the user triggers the Sopen action.
        """

        # Set open directory to directory of current .par file or to lastDir if there is no open file
        dir = os.path.dirname(self.fileName) if self.fileName is not None else self.lastDir
        
        # Open file
        formats = ["*.s2p" ]
        fname = unicode(QFileDialog.getOpenFileName(self, "%s - Open [S] Touchstone File" % applicationName, dir, "Touchstone files (%s)" % " ".join(formats)))
                    
        # If there is a file to open, read parameters. Otherwise (File Open dialog cancelled by user) do nothing
        if fname: 
            Sts = Stouchstone(fname)
            self.addResultToList( (None,  None,  None,  Sts,  None) )
            self.on_action_Compare_triggered(True)
            

    @pyqtSignature("bool")
    def on_action_FileNew_triggered(self,  checked):
        """
        Creates an empty instance of Parameters Class and opens dialog to edit parameters.
        This function is automatically executed by the GUI when the user triggers the FileNew action.
        """
        if self.paramEdit is not None:
            QMessageBox.warning(self, "WARNING",  'There is an open Parameter Edit Window. Close it before openning a new one.')
        else:   
            # Check if there are unsaved changes
            if not self.okToContinue():    
                return
            
            # Cleaup results and plots
            self.cleanup()
            
            self.P = Parameters(None)
            self.fileName = None
            self.updateStatus('New empty parameter set created')
            self.on_action_Edit_triggered(False)
            
            
    @pyqtSignature("bool")
    def on_action_ListAdd_triggered(self,  checked):
        """
        Store current result in self.results_comboBox list and self.listResults.
        @param checked = SIGNAL/SLOT parameter.
        """
        if self.P is None:
            QMessageBox.warning(self, "WARNING",  'No result to store. Open a parameter file and run synthesis first.')
            return
        
        if self.CP is None or self.CM is None or self.SP is None or self.FT is None:
            QMessageBox.warning(self, "WARNING",  'No result to store. Run synthesis first.')
            return
            
        # If parameterEditDlg is open and not recomputed, compute with dialog parameters.
        if self.paramEdit is not None and self.paramEdit.dialogModified is True and self.paramEdit.recomputed is False: 
            message = "Parameters have been modified and it is necessary to recompute\nRecompute synthesis?"
            reply = QMessageBox.question(self, "%s - Recompute" % applicationName, message, QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
            if reply in [QMessageBox.Cancel,  QMessageBox.No]:
                return
            elif reply == QMessageBox.Yes:
                self.paramEdit.on_compute_pushButton_clicked(True)
        
        self.addResultToList( (self.P,  self.CP,  self.CM,  self.SP,  self.FT) )
        

    @pyqtSignature("bool")
    def on_action_ListRemove_triggered(self,  checked):
        """
        Remove result matrix from self.results_comboBox and self.listResults lists.
        """
        if self.results_comboBox.count() == 0: 
            QMessageBox.warning(self, "WARNING",  'Results list is empty' )
            return
        
        index = self.results_comboBox.currentIndex()
        name = str(self.results_comboBox.currentText())
        
        #print 'Remove result %d: %s' % (index,  name)
        
        message = "Are you sure you what to remove result '%s' from list?\nRemove?" % name
        reply = QMessageBox.question(self, "%s - Remove result" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            self.results_comboBox.removeItem(index)
            del self.listResults[index]
            myPrint("Result '%s' removed from list" % name)
            
            if self.results_comboBox.count() > 0: 
                # Load the result corresponding to the new index in CM_comboBox
                self.on_results_comboBox_currentIndexChanged(self.results_comboBox.currentIndex())
                
            self.indexResults = self.results_comboBox.currentIndex()


    @pyqtSignature("int")    
    def on_results_comboBox_currentIndexChanged(self,  index):
        """
        Load a new result.
        This function is automatically executed by the GUI when the user changes the results list comboBox.
        It does nothing if self.noCleanup is True.
        """
        # This method is automatically called when on_action_ListAdd_triggered() appends an item to the comboBox list, or when results_comboBox.setCurrentIndex() is called below.
        # self.noCleanup will be True. In that case we not want to do nohing.
        if self.noCleanup: return
        
        # Get result
        index = self.results_comboBox.currentIndex()
        name = str(self.results_comboBox.currentText())
        result = self.listResults[index]
        
        # Check if result has P, CP, CM, and FT. Otherwise it cannot be loaded.
        if not all([result[0], result[1],  result[2],  result[4] ]):
            QMessageBox.warning(self, "WARNING",  """Result '%s' was read from [S] parameters Touchstone file and has no Characteristic Polynomials nor Coupling Matrices.<p>
                                            It cannot be loaded."""  % name)
            # Restore last index to comboBox
            self.noCleanup = True
            self.results_comboBox.setCurrentIndex(self.indexResults) # Will call self.on_action_ListRemove_triggered, which calls self.cleanup()
            self.noCleanup = False
            return

        # Check if parameters need saving.
        if not self.okToContinue():
            QMessageBox.warning(self, "WARNING",  'User cancelled action.<p>Result has not been loaded from list.' )
            # Restore last index to comboBox
            self.noCleanup = True
            self.results_comboBox.setCurrentIndex(self.indexResults) # Will call self.on_action_ListRemove_triggered, which calls self.cleanup()
            self.noCleanup = False
            return

        # Load results
        self.indexResults = index
        
        # BE CAREFUL: Add new elements to the tuple AT THE END.
        # Elements will be accessed later as SP = result[3]
        (self.P,  self.CP,  self.CM,  self.SP,  self.FT) = copy.deepcopy( result )
        
        # Update plots and edit windows if they are open.
        self.updateWindows()
        
        myPrint("Result '%s' loaded from list" % name)

    @pyqtSignature("bool")
    def on_action_Compare_triggered(self,  checked):
        """ 
        Compare results in list.
        This function is automatically executed by the GUI when the user toggles the 'Compare results' button.
        """   
        # Since this action does not update windows, it closes all windows first and, if checked, creates new windows.
        self.closeResultsWindow()
        
        if len(self.listResults) == 0:
            QMessageBox.warning(self,  "WARNING",  'Results list is empty. Nothing to plot.')
            self.action_Compare.setChecked(False)
            return
      
        # Compute [S] parameters from current coupling  matrix
        myPrint('Displayed results comparison:')
    
        # BE CAREFUL if somebody has inserted new elements in tuple
        freq =  [ result[3].freq / 1e9 for result in  self.listResults ]
        dataS11 = [ 20*np.log10(np.abs(result[3].S11)) for result in  self.listResults ]
        dataS21 = [ 20*np.log10(np.abs(result[3].S21)) for result in  self.listResults ]
        
        freqGD = [ result[3].freq2 / 1e9 for result in  self.listResults ]
        dataGD = [ 1e9 * result[3].groupDelay2 for result in  self.listResults ]
        
        phaseS11   = [ result[3].phaseS11 * 180/np.pi for result in  self.listResults  ]
        phaseS21   = [ result[3].phaseS21 * 180/np.pi for result in  self.listResults  ]
        
        resultNamesMag = [  unicode(self.results_comboBox.itemText(n) + ' (dB)') for n in range(self.results_comboBox.count()) ]
        resultNamesPhase = [  unicode(self.results_comboBox.itemText(n) + ' (phase)') for n in range(self.results_comboBox.count()) ]
        resultNamesGD = [  unicode(self.results_comboBox.itemText(n) + ' (GD)') for n in range(self.results_comboBox.count()) ]
        rightYVisible = [ False for n in range(self.results_comboBox.count())]
        
        # Set dynamic range to the lowest value of S21 at sweep range ends.
        dynamicRange = 0
        for S21 in dataS21: dynamicRange = max(dynamicRange ,  -20*log10( min([ abs(S21[0]), abs(S21[-1] )])) )
        dynamicRange = 10*ceil(dynamicRange/10)
        
        self.SPlotCompare = DbPlot(parent=self,  
                                                    windowTitle = '[S] comparison', 
                                                    tabTitle = 'S11', 
                                                    title = 'S<sub>11</sub> compared for different results',                                                          
                                                    XData = freq, leftYData = dataS11, 
                                                    rightYData = phaseS11,
                                                    rightYLabel = 'Phase(S<sub>11</sub>) (deg)' , rightYNames = resultNamesPhase, rightYVisible = rightYVisible, 
                                                    leftYmax = 0,  leftYmin = -dynamicRange, leftYNames = resultNamesMag, XLabel = 'Frequency (GHz)', leftYLabel = 'dB', 
                                                    Xunits = 'GHz',  leftYunits = 'dB',  rightYunits = 'deg' )        

        self.SPlotCompare.addTab(tabTitle = 'S21',
                                                    title = 'S<sub>21</sub> compared for different results', 
                                                    XData = freq, leftYData = dataS21, 
                                                    rightYData = phaseS21,
                                                    rightYLabel = 'Phase(S<sub>21</sub>) (deg)' , rightYNames = resultNamesPhase, rightYVisible = rightYVisible, 
                                                    leftYmax = 0,  leftYmin = -dynamicRange, leftYNames = resultNamesMag,
                                                    XLabel = 'Frequency (GHz)', leftYLabel = 'dB', 
                                                    Xunits = 'GHz',  leftYunits = 'dB',  rightYunits = 'deg' )

        self.SPlotCompare.addTab(tabTitle = 'Group delay',
                                                    title = 'Group delay compared for different results',                                                          
                                                    XData = freqGD, leftYData = dataGD, 
                                                    leftYNames = resultNamesGD, XLabel = 'Frequency (GHz)', leftYLabel = 'Group delay (ns.)', 
                                                    Xunits = 'GHz',  leftYunits = 'ns' )
                                                                
        
##@}

# End class: MainWindow   #
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#


#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
# Begin class: HelpForm

class HelpForm(QDialog):
    """
    GUI dialog to display help
    """    

    def __init__(self, page, parent=None):
        """
        Constructor: Creates dialog window to display html help file.
            @param page   = html file name, relative to the program folder.
            @param parent = Parent widget, in this case is coupling matrix mainWindow.
        """
        super(HelpForm, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_GroupLeader)
        
        ##
        # Copy of the mainWindow class instance.
        # It is necessary  to store a copy since it must be used to access actions and resources.
        self.mainWindow = parent 

#   These actions have been already created in Qt Designer
#        backAction = QAction(QIcon(":/back.png"), self.tr("&Back"), self)
#        homeAction = QAction(QIcon(":/home.png"), self.tr("&Home"), self)
        backAction = self.mainWindow.action_Back
        homeAction = self.mainWindow.action_Home
        
        backAction.setShortcut(QKeySequence.Back)
        homeAction.setShortcut(self.tr("Home"))
        self.pageLabel = QLabel()

        toolBar = QToolBar()
        toolBar.addAction(backAction)
        toolBar.addAction(homeAction)
        toolBar.addWidget(self.pageLabel)
        self.textBrowser = QTextBrowser()

        layout = QVBoxLayout()
        layout.addWidget(toolBar)
        layout.addWidget(self.textBrowser, 1)
        self.setLayout(layout)

        self.connect(backAction, SIGNAL("triggered()"), self.textBrowser, SLOT("backward()"))
        self.connect(homeAction, SIGNAL("triggered()"), self.textBrowser, SLOT("home()"))
        self.connect(self.textBrowser, SIGNAL("sourceChanged(QUrl)"), self.updatePageTitle)

        self.textBrowser.setSearchPaths([":/help/Help"])    # Path ":/" is the resource file root, path "./" is directory where the application was called
        self.textBrowser.setSource(QUrl(page))
        self.resize(1024, 700)
        self.setWindowTitle(self.tr("%1 Help").arg(QApplication.applicationName()))


    def updatePageTitle(self):
        self.pageLabel.setText(self.textBrowser.documentTitle())

# End class: HelpForm
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#

class SetCouplingDlg(QDialog):
    """
    Dialog to set the type of a coupling.
    """

    def __init__(self, item, topolText,  topolColors, parent=None):
        """
        Create dialog to set the type of a coupling.
        @param item = Topology table item to modify
        @param topolText = Topology table text, corresponding to ComboBox items
        @param topolColors = Topology table colors, corresponding to ComboBox items
        @param parent = parent widget (topology table)
        """
        super(SetCouplingDlg, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        ##
        # Topology table widget
        self.mainWindow = parent
        
        ##
        # Topology table item to set
        self.item = item
        
        ##
        # ComboBox item text for different kinds of coupling
        self.typeLabels = ['Uncoupled',  'Real',  'Real+', 'Real-',  'Imag+',  'Complex', 'Complex+', 'Complex-']
        
        ##
        # Topology table text, corresponding to ComboBox items
        self.topolText = topolText
        
        ##
        # Topology table color, corresponding to ComboBox items
        self.topolColors = topolColors
        
        self.label = QLabel('Type of coupling:')
        self.comboBox = QComboBox(self)
        self.comboBox.addItems(self.typeLabels)
        
        # Set table item value as comboBox current item
        entriesDict = dict(zip(self.topolText, self.typeLabels))
        self.comboBox.setCurrentIndex( self.comboBox.findText( entriesDict[ str(item.text()) ] ) )
        
        layout = QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.comboBox)
        
        self.setWindowTitle("Cross couplings")
        self.setToolTip("""Choose the type of cross coupling: 
The real part is a conventional (inductive or capacitive)
coupling and the imaginary part a resistive coupling. 
The imag part will always be positive (passive circuit),
while the real part can forced to positive, negative or 
have any sign.""")
        
        self.connect(self.comboBox, SIGNAL("currentIndexChanged(int)"),  self.typeSelected)
        
        
    def typeSelected(self,  index):
        """
        Set type selected in self.comboBox to self.item
        """
        row = self.mainWindow.topology_tableWidget.currentRow()
        col = self.mainWindow.topology_tableWidget.currentColumn()
        
        if row != col: # Do not change main diagonal
            itemReciprocal = self.mainWindow.topology_tableWidget.item(col, row)
            for it in [self.item,  itemReciprocal]:
                it.setText(self.topolText[index])
                it.setBackground(QBrush(QColor(self.topolColors[index])))
        
        self.close()        


#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
# Begin class: MatrixEditDlg

class MatrixEditDlg(QDialog, Ui_matrixeditdlg.Ui_MatrixEditDlg):
    """
    GUI dialog to edit coupling matrix
    """
    
    def __init__(self, parent):
        """
        Constructor: Creates dialog window to edit current coupling matrix.
            @param parent = Parent widget, in this case is coupling matrix mainWindow.
        """
        super(MatrixEditDlg, self).__init__(parent)
        # Caution: do not set Qt.WA_DeleteOnClose attribute. The class instance must not be deleted when closing the dialog.
        
        ##
        # Copy of the coupling matrix mainWindow class instance.
        # It is necessary  to store a copy since it must be accessed by some member functions.
        self.mainWindow = parent
        
        self.setupUi(self)        
        
        ##
        # String that explains the last edit action. This is what is pushed into the edit action list after edit actions.
        self.lastEditAction = ''  
        
        ##
        # Shallow copy of the CM.bkpMatQ list, for easier access.
        self.bkpMatQ = self.mainWindow.CM.bkpMatQ
        
        ##
        # Shallow copy of the CM.bkpActionslist, for easier access.
        self.bkpActions = self.mainWindow.CM.bkpActions
        
        # Initialize edit list with previous list of edit actions
        assert self.edit_listWidget.count() == 0    # List should be empty here. We check it because list is not destructed on close.
        self.edit_listWidget.insertItems(0, self.bkpActions)
        
        ##
        # Variable to store a list of widgets with bad input.
        # Only when this list is empty, he "Accept" and "Compute" buttons are enabled.
        self.badInput = [ ]
        
        # Set maximum row and column indices in matrixEditDlg spinBoxes
        self.setMatrixSize()
        
        # Prepare rotation angle lineEdit. Initial value equal to zero and cannot be empty.
        self.rot_angle_lineEdit.setText('0')
        self.rot_angle_lineEdit.setValidator( QRegExpValidator( QRegExp(stComplex),  self) )
        
        # Prepare optimization Q. Initial value equal to zero and cannot be empty.
        self.Q_doubleSpinBox.setValue( np.mean(np.abs(self.mainWindow.CM.MatQ.Q)) )
        
        # Prepare Qeff for prescribed flatness
        self.flatnessQeff_lineEdit.setValidator( QRegExpValidator( reRealPositiveInf,  self) )
        
        # Optimization comboBox and stackedWidget
        optimMethod = 0
        self.optim_comboBox.setCurrentIndex(optimMethod)
        self.optim_stackedWidget.setCurrentIndex(optimMethod)
        
        # Matrix topology table
        self.topology_tableWidget = myTableWidget(name = "topology",  parent = self.optim_page)
        self.topology_tableWidget.setColumnCount(0)
        self.topology_tableWidget.setRowCount(0)
        
        self.topology_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.topology_tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
        
        self.topology_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum,  QSizePolicy.Maximum)) 
        
        self.topology_tableWidget.setToolTip("""<b>Coupling matrix topology: </b>
<p>        
Define here the position of purely real, purely imaginary 
or complex non-diagonal coupling matrix entries.
<p>
Diagonal entries corresponding to resonant nodes are always complex, with imaginary 
part determined by finite Q of resonators and real part a degree of freedom.
<p>
Diagonal entries corresponding to non-resonant nodes are always purely imaginary negative.
<p>
Real part of non-diagonal entries can be forced to be positive (R+ or C+)
or negative (R- or C-), to have a electric or a magnetic coupling.
<p>
Imaginary part of complex or purely imaginary non-diagonal entries is always positive
to have a passive circuit.
""")
        
        self.verticalLayout_13.addWidget(self.topology_tableWidget,  100)   # stretchFactor=100
        self.verticalLayout_13.addStretch()
   
        # Make topology table as large as possible
        self.verticalLayout_13.setStretchFactor(self.topology_tableWidget,  100)   

        # Make edit toolBox as large as possible, to fit the topology table
        self.verticalLayout_12.setStretchFactor(self.edit_toolBox,  10)
        
        # Connect slot
        self.connect(self.topology_tableWidget,  SIGNAL("itemClicked(QTableWidgetItem*)"),  self.topologyItemClicked)
        
        ##
        # Text corresponding to couplings type in topology table
        self.topolText = [ '0',  'R', 'R+', 'R-', 'I+',  'C', 'C+', 'C-', '']
        
        ##
        # Colors corresponding to couplings type in topology table
        self.topolColors = [ Qt.white, Qt.blue, Qt.green, Qt.cyan, Qt.yellow, Qt.darkRed, Qt.red, Qt.magenta, Qt.black ]
        
        # Fill in Matrix topology table
        self.udpateTopology()
        
        ##
        # Variable to store the modified/unmodifed status of the dialog widgets.
        # The dialogChanged() method is automatically called when the user modifies the edit widgets contents and sets this variable to True.
        # This variable must be set to False at the end of the __init__ dialog constructor, 
        # since dialogChanged() is called when the constructor sets the edit widgets contents.
        # This attribute is defined in this class only for compatibility with the dialogChanged() method,
        # which has been copied literally (except for the name of pushButtons) from ParamEditDlg class.
        self.dialogModified = False
        
        
    def udpateTopology(self):
        """
        Fill in Matrix topology table from values in self.mainWindow.CM.MatQ
        """
        table = self.topology_tableWidget
        labels = self.mainWindow.rowIndices
        
        M = self.mainWindow.CM.MatQ.M
        
        size = len(labels)
        table.setColumnCount(size)
        table.setRowCount(size)
        table.setHorizontalHeaderLabels( labels )
        table.setVerticalHeaderLabels( labels )
        
        # Threshold to consider the real or imaginary parts of a matrix element positive, negative or non-zero
#        eps = self.mainWindow.CM.matrixElementsEps('Matrix topology')
        eps = 10** -self.mainWindow.CM_prec.value()     # Keep matrix topology in sync which what is displayed in the matrix table
        
        for m in range(size):
            for n in range(size):
                if n != m: 
                    if np.abs(M[m, n].real) < eps:
                        if np.abs(M[m, n].imag) < eps:  type = 0    # Uncoupled
                        else:                                          type = 4     # Imaginary (positive)
                    else:
                        if np.abs(M[m, n].imag) < eps:  
                            if M[m, n].real > 0:                type = 2    # Real positive
                            else:                                      type = 3    # Real negative
                        else:                                          
                            if M[m, n].real > 0:                type = 6     # Complex positive
                            else:                                      type = 7     # Complex negative
                else:                                                  type = 8     # Main diagonal
                
                st = self.topolText[type]
                brush = self.topolColors[type]
                item = QTableWidgetItem(st) 
                item.setTextAlignment(Qt.AlignHCenter|Qt.AlignVCenter)
                item.setBackground(brush)
                
#                # This is set for the whole table in MatrixEditDlg.__init__()
#                if n != m:
#                    item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsSelectable)
#                else:       
#                    item.setFlags( Qt.NoItemFlags )

                table.setItem(m, n, item)
                
        table.resizeRowsToContents()
        table.resizeColumnsToContents()
        table.setMaximumSize( table.maximumSizeHint() )            
        
        
    def getTopologyFlags(self):
        """
        Return matrices of flags that describe the topology of the real part and the imaginary part of the coupling matrix,
        based on the entries in the topology table.
        @return topologyReal = Numpy matrix of Bool type that has True values for elements with non-zero real part.
        @return topologyImag = Numpy matrix of Bool type that has True values for elements with non-zero imag part.
        @return topologySignReal = Int numpy array having -1,1 or 0 values at the position of coupling matrix entries with negative, positive or any real part, respectively.
        @return topologySignImag = Int numpy array having -1,1 or 0 values at the position of coupling matrix entries with negative, positive or any imaginary part, respectively.
        """
        table = self.topology_tableWidget
        Nrows = table.rowCount()
        Ncols = table.columnCount()
        assert Nrows == Ncols
        
        topologyReal = np.zeros( (Nrows,Ncols),  dtype = bool)
        topologyImag = np.zeros( (Nrows,Ncols),  dtype = bool)
        topologySignReal  = np.zeros((Nrows,Ncols), dtype='int') # Int numpy array having -1,1 or 0 values at the position of coupling matrix entries that must be negative, positive or any, respectively.
        topologySignImag = np.zeros((Nrows,Ncols), dtype='int') # Int numpy array having -1,1 or 0 values at the position of coupling matrix entries that must be negative, positive or any, respectively.
        
        for m in range(Nrows):
            for n in range(Ncols):
                if n==m: continue       # Diagonal elements are not set in the GUI
                st = str(table.item(m, n).text())
                type = self.topolText.index(st)
                topologyReal[m, n] = type in [1, 2, 3, 5, 6, 7]
                topologyImag[m, n] = type in [4, 5, 6, 7]
                if topologyImag[m, n]: topologySignImag[m, n] = 1      # All non-diagonal imaginary parts must be positive to have a passive filter.
                if     type in [2, 6]: topologySignReal[m, n] = 1   # Positive real part
                elif  type in [3, 7]: topologySignReal[m, n] = -1  # Negative real part
                elif  type in [1, 5]: topologySignReal[m, n] = 0    # Any real part
                else: topologySignReal[m, n] = 0                         # Zero real part
                
        return topologyReal,  topologyImag, topologySignReal, topologySignImag
        
    def topologyItemClicked(self,  item):
        """
        Update the contents of self.topology_tableWidget after the user has clicked an item.
        """
        row = self.topology_tableWidget.currentRow()
        col = self.topology_tableWidget.currentColumn()
        self.topology_tableWidget.setFocus(Qt.MouseFocusReason)
        
        if row != col:  # Do not change main diagonal
            couplingsDlg = SetCouplingDlg(item,  self.topolText, self.topolColors,  self)
            couplingsDlg.exec_()
        
        
    def setMatrixSize(self):
        """
        Set maximum row and column indices in matrixEditDlg spinBoxes, according to size of self.mainWindow.CM.MatQ.M.
        """
        # Set row and column indices comboBoxes
        (M, N) = self.mainWindow.CM.MatQ.M.shape
        
        ind = self.rot_pivotM_comboBox.currentIndex()
        self.rot_pivotM_comboBox.clear()
        self.rot_pivotM_comboBox.addItems(self.mainWindow.rowIndices)
        self.rot_pivotM_comboBox.setCurrentIndex(ind)
        
        ind = self.rot_pivotN_comboBox.currentIndex()
        self.rot_pivotN_comboBox.clear()
        self.rot_pivotN_comboBox.addItems(self.mainWindow.rowIndices)
        self.rot_pivotN_comboBox.setCurrentIndex(ind)
        
        ind = self.ann_pivotM_comboBox.currentIndex()
        self.ann_pivotM_comboBox.clear()
        self.ann_pivotM_comboBox.addItems(self.mainWindow.rowIndices)
        self.ann_pivotM_comboBox.setCurrentIndex(ind)
        
        ind = self.ann_pivotN_comboBox.currentIndex()
        self.ann_pivotN_comboBox.clear()
        self.ann_pivotN_comboBox.addItems(self.mainWindow.rowIndices)
        self.ann_pivotN_comboBox.setCurrentIndex(ind)
        
        ind = self.ann_elemM_comboBox.currentIndex()
        self.ann_elemM_comboBox.clear()
        self.ann_elemM_comboBox.addItems(self.mainWindow.rowIndices)
        self.ann_elemM_comboBox.setCurrentIndex(ind)
        
        ind = self.ann_elemN_comboBox.currentIndex()
        self.ann_elemN_comboBox.clear()
        self.ann_elemN_comboBox.addItems(self.mainWindow.rowIndices)
        self.ann_elemN_comboBox.setCurrentIndex(ind)
        
        ind = self.scale_node_comboBox.currentIndex()
        self.scale_node_comboBox.clear()
        self.scale_node_comboBox.addItems(self.mainWindow.rowIndices)
        self.scale_node_comboBox.setCurrentIndex(ind)      
    ##@{
    # @name  Functions automatically excuted when the user interacts with the GUI 
        
    
    def dialogChanged(self):
        """
        Custom slot in Qt designer connected to widgets "changed" signal.
        This function is automatically executed by the GUI every time the user changes dialog values,
        and sets the self.dialogModified variable to True.
        """
        self.dialogModified = True
        if hasattr(self.sender(),  'validator'):
            validator = self.sender().validator()
            if validator is not None: 
                result = validator.validate(self.sender().text(),  0)[0]
                palette = self.sender().palette()
                if result == 1: 
                    palette.setColor(QPalette.Text,  Qt.red)
                    self.sender().setPalette(palette)
                    self.badInput.append(self.sender()) 
                    self.sender().setFocus(Qt.OtherFocusReason)    # It does nothing, because it is executed before focus changes to another widget.
                if result == 2: 
                    palette.setColor(QPalette.Text,  self.mainWindow.palette().text().color())
                    self.sender().setPalette(palette)
                    while self.sender() in self.badInput: self.badInput.remove(self.sender()) 
        
        # For debugging
        #print [ unicode(n.text()) for n in self.badInput]
        
        if len(self.badInput) > 0:
            self.apply_pushButton.setEnabled(False)
            self.undo_pushButton.setEnabled(False)
        else:
            self.apply_pushButton.setEnabled(True)
            self.undo_pushButton.setEnabled(True)
    
    @pyqtSignature("QListWidgetItem*")
    def on_edit_listWidget_itemDoubleClicked(self,  item):
        """
        Load coupling matrix saved in backup list
        """
        st = item.text()
        index = self.edit_listWidget.row(item)
        self.mainWindow.CM.MatQ = self.bkpMatQ[index+1].copy()   # Deep copy because MatQ will be modified by edit actions. The first matrix in list was loaded before any edit actions.
        self.mainWindow.loadMatrixQ(self.mainWindow.CM.MatQ)
        self.lastEditAction = "Matrix reverted to backup state: '%s'" % st
        self.mainWindow.pushEditAction()
        

    @pyqtSignature("bool")
    def on_undo_pushButton_clicked(self,  checked):        
        """
        Undo last edit action from edit actions list. Pops the edit action list and the backup Qmatrices list.
        """
        # Number of edit actions saved
        count = self.edit_listWidget.count()
        assert count == len( self.bkpMatQ)-1
        
        if count == 0:
            QMessageBox.warning(self, "WARNING",  "There are no edit actions to undo.")
        else:
            badMatQ = self.bkpMatQ.pop()                    # Discard last modified matrix from the backup list
            del badMatQ                                                    # Just in case garbage collection fails.
            MatQ = self.bkpMatQ[-1].copy()                      # Get the last valid matrix. Deep copy because MatQ will be modified by edit actions.
            self.mainWindow.CM.MatQ = MatQ                            # Update the CM.MatQ attribute with reference to the new MatQ.
            self.mainWindow.loadMatrixQ(MatQ)                           # Load the new MatQ in table widgets.
            item = self.edit_listWidget.takeItem(count-1)   # Remove last edit action from edit actions list.
            self.bkpActions.pop()
            myPrint("Undo: Matrix Edit action '%s'" % item.text())
            del item                                                             # According to Qt4 doc, taken items must be deleted manually.
        

    @pyqtSignature("bool")
    def on_clear_pushButton_clicked(self,  checked):        
        """
        Clear edit actions list. Clears edit action list and the backup Qmatrices list.
        """
        
        message = "Do you really want to clear the action list for undo?"
        reply = QMessageBox.question(self, "%s - Clear actions list" % applicationName, message, QMessageBox.Yes | QMessageBox.Cancel)
        if reply == QMessageBox.Cancel:
            return
        
        self.mainWindow.CM.bkpMatQ = [ self.mainWindow.CM.MatQ.copy() ] 
        self.mainWindow.CM.bkpActions = [ ]
        
        self.bkpMatQ = self.mainWindow.CM.bkpMatQ
        self.bkpActions = self.mainWindow.CM.bkpActions
        
        # Remove all list items
        while self.edit_listWidget.count() > 0: 
            item = self.edit_listWidget.takeItem(0) 
            del item
        

    @pyqtSignature("bool")
    def on_apply_pushButton_clicked(self,  checked):        
        """
        Apply selected action to the matrix.
        The coupling matrix self.dirty attribute is set to True.
        """
        MatQ = self.mainWindow.CM.MatQ
        
        try:
            action = unicode(self.edit_toolBox.itemText(self.edit_toolBox.currentIndex()))
            if action == u'Rotate matrix':
                if self.rot_trigo_radioButton.isChecked():         rotationType = 'trigonometric'
                elif self.rot_hyper_radioButton.isChecked():    rotationType = 'hyperbolic'
                else: raise synthError,  "No rotation type selected"
                i = self.rot_pivotM_comboBox.currentIndex()
                j = self.rot_pivotN_comboBox.currentIndex()
                rotAng = complex( str(self.rot_angle_lineEdit.text()).replace(' ', '').replace('i', 'j') ) *pi/180.0  # Rotate angle is in deg in the GUI
#                (M, N) = MatQ.M.shape
#                if i>=M or j>=N: raise synthError,  'Pivot indices (%d,%d) larger than matrix size' % (i, j)
                MatQ.rotateMatrix(rotationType, i, j, rotAng, flagQ=True)
#                MatQ.mpRotateMatrix(rotationType, i, j, rotAng, flagQ=True)
                self.lastEditAction = '%s matrix rotation with pivot (%s,%s) and angle %g %+gj deg' % (rotationType.title(), self.mainWindow.rowIndices[i], self.mainWindow.rowIndices[j], rotAng.real *180/pi,  rotAng.imag*180/pi ) # Rotate angle is in deg in the GUI
                
            elif action == 'Annihilate element':
                if self.ann_trigo_radioButton.isChecked():         rotationType = 'trigonometric'
                elif self.ann_hyper_radioButton.isChecked():    rotationType = 'hyperbolic'
                else: raise synthError,  "No rotation type selected"
                i = self.ann_pivotM_comboBox.currentIndex()
                j = self.ann_pivotN_comboBox.currentIndex()
                m = self.ann_elemM_comboBox.currentIndex()
                n = self.ann_elemN_comboBox.currentIndex()
#                (M, N) = MatQ.M.shape
#                if i>=M or j>=N: raise synthError,  'Pivot indices (%d,%d) larger than matrix size' % (i, j)
#                if m>=M or n>=N: raise synthError,  'Element indices (%d,%d) larger than matrix size' % (i, j)
                rotAng = MatQ.rotAngleEliminate(rotationType, i, j, m, n)
                MatQ.rotateMatrix(rotationType, i, j, rotAng, flagQ=True)
                
                self.lastEditAction = '%s matrix rotation with pivot (%s,%s) and angle %g %+gj deg to annihilate element (%s,%s)' % (rotationType.title(),  self.mainWindow.rowIndices[i], self.mainWindow.rowIndices[j],   rotAng.real *180/pi,  rotAng.imag*180/pi, self.mainWindow.rowIndices[m], self.mainWindow.rowIndices[n])  # Rotate angle is in deg in the GUI
                
            elif action == 'Add nodes':
                if self.add_source_radioButton.isChecked(): 
                    position = 'source'
                    self.lastEditAction = 'Node added at source side'
                elif self.add_load_radioButton.isChecked(): 
                    position = 'load'
                    self.lastEditAction = 'Node added at load side'
                elif self.add_both_radioButton.isChecked(): 
                    position = 'both'
                    self.lastEditAction = 'Nodes added at both sides' 
                else: raise synthError,  "Add nodes: No new node position selected"
                MatQ.inflateMatrix(position)
                
            elif action == 'Node scaling':
                factor = self.scale_factor_doubleSpinBox.value()
                node = self.scale_node_comboBox.currentIndex()
                MatQ.scaleNode(node,  factor)
                self.lastEditAction = 'Node [%s] scaled with factor %g' % (self.mainWindow.rowIndices[node],  factor)
                
            elif action == 'Prescribed flatness':
                Qeff = float(self.flatnessQeff_lineEdit.text())
                MatQ.addLossesQeff(Qeff)
                self.lastEditAction = 'Prescribed flatness with Qeff = %g' % (Qeff)
            
            elif action == 'Optimization':
                
                Q =  self.Q_doubleSpinBox.value()
                topologyReal, topologyImag, topologySignReal, topologySignImag = self.getTopologyFlags()
                
                algor = self.algor_comboBox.currentText()
                maxIter = self.maxIter_spinBox.value()
                Nsamples = self.Nsamples_spinBox.value()
                weights = [ self.wS11_doubleSpinBox.value(),  self.wS22_doubleSpinBox.value(), self.wS21_doubleSpinBox.value(), self.wGD_doubleSpinBox.value() ]
                
                optimMethod = unicode(self.optim_comboBox.currentText())
                if optimMethod == u'Characteristic Polynomials':
                    S11DR = self.S11DR_doubleSpinBox.value()
                    S21DR = self.S21DR_doubleSpinBox.value()
                    app.setOverrideCursor(Qt.WaitCursor)
                    MatQ.uniformQ(self.mainWindow.mainWindow.P, self.mainWindow.mainWindow.CP, weights, Q, algor, maxIter, Nsamples, S11DR, S21DR, topologyReal, topologyImag, topologySignReal, topologySignImag)
                    app.restoreOverrideCursor()
                    self.lastEditAction = 'Matrix optimization (CP) with parameters: alg=%s, maxIter=%d, Nsamples=%d, Q=%g, w=%s, N=%d, S11DR=%g, S21DR=%g' % (algor, maxIter, Nsamples, Q, listStr(weights, 2),  Nsamples, S11DR, S21DR)
                    
                elif optimMethod == u'Specification mask':
                    SM = self.mainWindow.mainWindow.SM
                    
                    if SM is None:
                        reply = QMessageBox.question(self, "WARNING",  'No specification mask loaded.<p>Do you want to open an specification mask file now?', QMessageBox.Yes| QMessageBox.Cancel)
                        if reply == QMessageBox.Yes:
                            self.mainWindow.mainWindow.on_action_Mask_triggered(True)
                            SM = self.mainWindow.mainWindow.SM
                    
                    if SM is not None:
                        app.setOverrideCursor(Qt.WaitCursor)
                        MatQ.uniformQMask(self.mainWindow.mainWindow.P, self.mainWindow.mainWindow.CP, weights, Q, algor, maxIter, Nsamples, SM, topologyReal, topologyImag, topologySignReal, topologySignImag)
                        app.restoreOverrideCursor()
                        self.lastEditAction = 'Matrix optimization (mask) with parameters: alg=%s, maxIter=%d, Nsamples=%d, Q=%g, w=%s, mask=%s' % (algor, maxIter, Nsamples, Q, listStr(weights, 2), SM.fileName)
                    else:
                        myPrint('Matrix optimization (mask) cancelled by user')
                        return # Matrix edit action has been canceled and we do not want to run the code below
                
                else: 
                    QMessageBox.warning(self,  "WARNING",  'Unknown optimization method: %s' % optimMethod)
                
                
                
            else:
                raise synthError,  "Unknown matrix edit action '%s'" % action
                
            MatQ.Qresonators()
            self.mainWindow.loadMatrixQ(MatQ)
            self.mainWindow.pushEditAction()
            self.mainWindow.dirty = True
            self.mainWindow.setWindowModified(self.mainWindow.dirty)
        
        except synthError,  err:
            app.restoreOverrideCursor()
            QMessageBox.critical(self,  "ERROR", '<p align="center">Matrix editor error:<br>%s</p>' % err )
            return
        

    def closeEvent(self,  event):
        """
        Reimplementation of the window close function.
        Sets couplingMatrixDlg toolbar Edit action to unchecked.
        """
        self.mainWindow.action_Edit.setChecked(False)
        event.accept()
            
    ##@}
        

# End class: MatrixEditDlg
#!!!!!!!!!!!!!!!!!!!!!!!!!#


#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
# Begin class: PredisZeorsDlg

class PredisZeorsDlg(QDialog):
    """
    Dialog to select predistortion zeros.
    """

    def __init__(self, roots_NRP, zerosArrang, parent=None):
        """
        Create dialog to select visible curves.
        @roots_NRP    = Roots of F(s)F(-s) that are in the left-hand s-plane
        @zerosArrang  = Predistiortion zeros arrangement (list of bool).
        @param parent = Parent window (default None).
        """
        super(PredisZeorsDlg, self).__init__(parent)
        
        ##
        # Handle to the dbplot main window.
        self.mainWindow = parent
        
        self.zerosArrang = zerosArrang
        
        layV = QVBoxLayout(self)    # Vertical layout containing everything
        title = QLabel("<b>Predistortion synthesis:</b>")
        title.setAlignment(Qt.AlignHCenter)
        layV.addWidget(title)
        layV.addWidget(QLabel("Select zeros of F(s)F(-s) in the"))
        layV.addWidget(QLabel("left s-plane that will go to F(s)"))
                
        layH1 = QHBoxLayout()   # Horizontal layout containing grid and stretch
        grid = QGridLayout()
       
        for n in range(self.mainWindow.P.N):
            grid.addWidget(QLabel(complexStr(roots_NRP[n], 3)), n, 0,  Qt.AlignRight)
            checkBox = QCheckBox()
            checkBox.setChecked(self.zerosArrang[n])
#            self.connect(checkBox, SIGNAL("stateChanged(int)"), lambda value: self.setZeroToFs(n,  bool(value)))
            self.connect(checkBox, SIGNAL("stateChanged(int)"), partial(self.setZeroToFs,  n) )
            grid.addWidget(checkBox, n, 1, Qt.AlignHCenter)
            
        layH1.addLayout(grid)
        layH1.addStretch()
        
        layV.addLayout(layH1)
        layV.addStretch()
        
        #buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
        buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
        self.connect(buttonBox,  SIGNAL("accepted()"), self, SLOT("accept()"))
        self.connect(buttonBox,  SIGNAL("rejected()"), self, SLOT("reject()"))
        
        layV.addWidget(buttonBox)
        
        self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,  QSizePolicy.Fixed))
        self.setWindowTitle("Zeros F(s)")
        self.setToolTip("""Select predistiortion zeros of F(s)F(-s) in the left-hand s-plane that will go to F(s).
If they are not selected, they will go to F(-s).
The zeros of F(s)F(-s) in the right-hand s-plane will go to the opposite polynomial.""")

    def setZeroToFs(self, n,  zeroToFs):
        self.zerosArrang[n] = bool(zeroToFs)

# End class: PredisZeorsDlg
#!!!!!!!!!!!!!!!!!!!!!!!!!#        
        

#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
# Begin class: SensitivitySPar

class SensitivityAnalysis(object):
    """
    Class for sensitivity analysis.
    """
    
    def __init__(self, parent):
        """
        Constructor: creates SensitivityAnalisys class instance.
            @param parent = Parent widget, in this case is coupling matrix mainWindow.
        """
        ##
        # Copy of the coupling matrix mainWindow class instance.
        # It is necessary  to store a copy since it must be accessed by some member functions.
        self.mainWindow = parent
        
        ##
        # QwtPlot that contains the Magnitude and Phase S-parameter plot for all random realizations. 
        self.SPlotMagPhase = None
        
        ##
        # Numpy array with columns containing S11 for each random realization.
        self.S11M = None
        
        ##
        # Numpy array with columns containing S21 for each random realization.
        self.S21M = None
        
        ##
        # Numpy array with columns containing S22 for each random realization.
        self.S22M = None
        
        ##
        # Numpy array with columns containing phase of S11 for each random realization.
        self.phaseS11M = None
        
        ##
        # Numpy array with columns containing phase of S21 for each random realization.
        self.phaseS21M = None
        
        ##
        # Numpy array with columns containing phase of S22 for each random realization.
        self.phaseS22M = None
        
        ##
        # Numpy array with columns containing groupDelay for each random realization.
        self.groupDelayM = None
        
        ##
        # [S] parameters class instance, used to compute the [S] parameters after random variations in coupling matrix.
        self.SP = self.mainWindow.CM.SP
        
        
    def runAnalysis(self, N, inout, freq,  Q, inductive, capacitive, resistive):        
        """
        Run Monte Carlo analysis with random variations in coupling matrix elements.
        Calls self.randomVariations() to obtain a randomly modified coupling matrix with maximum element variations determined by function arguments.
        @param N = Number of Monte Carlo random realizations.
        @param inout = Maximum random variation in % of nominal value, for input/output (source/load) couplings. It is applied to elements in first and last rows and columns.
        @param freq = Maximum random variation in % of nominal value, for resonators resonant frequency.
        @param Q = Maximum random variation in % of nominal value, for resonators quality factor.
        @param inductive = Maximum random variation in % of nominal value, for inductive couplings. It is applied to positive real parts of out-of-diagonal matrix elements.
        @param capacitive = Maximum random variation in % of nominal value, for capacitive couplings. It is applied to negative real parts of out-of-diagonal matrix elements.
        @param resistive = Maximum random variation in % of nominal value, for resistive couplings. It is applied to imaginary parts of out-of-diagonal matrix elements.
        """
        app.setOverrideCursor(Qt.WaitCursor)
        MatQ = self.mainWindow.CM.MatQ  # Pointer to non-modified CouplingMatrix
        # Prepare arrays having the [S] parameters of random realizations in columns
        nFreq = len(self.SP.freq)
        nFreq2 = nFreq-1    # Number samples for group delay computed from M, groupDelayM
        self.S11M,  self.S21M,  self.S22M = np.zeros((nFreq, N),  dtype=np.complex128),  np.zeros((nFreq, N),  dtype=np.complex128), np.zeros((nFreq, N),  dtype=np.complex128)
        self.phaseS11M,  self.phaseS21M,  self.phaseS22M,  self.groupDelayM = np.zeros((nFreq, N)),  np.zeros((nFreq, N)), np.zeros((nFreq, N)), np.zeros((nFreq2, N))
        
        maxModReal,  maxModImag = np.zeros(MatQ.M.shape),  np.zeros(MatQ.M.shape)
        maxModQn,  maxModF0 = np.zeros(MatQ.Q.shape),  np.zeros(MatQ.Q.shape)
        first = MatQ.extraNodesS        # Index of first  resonant node
        last = MatQ.M.shape[0] - MatQ.extraNodesL    # Index of last  resonant node + 1
        Qn = MatQ.Q
        f0n = MatQ.FT.unormFreq(np.diag(MatQ.M)[first:last].real)
        
        for n in range(N):
            # Copy of current coupling matrix, to which random variations are applied
            modMatQ = self.randomVariations(MatQ, inout, inductive, capacitive,  resistive, freq, Q )
            self.SP.fromCouplingMatrix(modMatQ)  # Do not update CM_error here, matrix elements have been modified ramdomly
            
            # Compute maximum variations, just for verification
            maxModReal = np.maximum(maxModReal,   100* np.abs(modMatQ.M.real / MatQ.M.real - 1) )
            maxModImag = np.maximum(maxModImag,  100* np.abs(modMatQ.M.imag / MatQ.M.imag - 1) )
            modQn = modMatQ.Q
            modF0n = MatQ.FT.unormFreq(np.diag(modMatQ.M)[first:last].real)
            maxModQn = np.maximum(maxModQn, 100*np.abs(modQn/Qn - 1) )
            maxModF0 = np.maximum(maxModF0, 100*np.abs(modF0n/f0n - 1) )
            
            self.S11M[:, n], self.S21M[:, n], self.S22M[:, n],  self.groupDelayM[:, n] = self.SP.S11M,  self.SP.S21M, self.SP.S22M,  self.SP.groupDelayM
            self.phaseS11M[:, n], self.phaseS21M[:, n], self.phaseS22M[:, n] = self.SP.phaseS11M,  self.SP.phaseS21M, self.SP.phaseS22M
            
        # Print maximum variations
        # Replace NaN's and Inf's with zero
        maxModReal[np.isnan(maxModReal)] = 0
        maxModReal[np.isinf(maxModReal)] = 0
        maxModImag[np.isnan(maxModImag)] = 0
        maxModImag[np.isinf(maxModImag)] = 0
        
        rowIndices = self.mainWindow.rowIndices
        myPrint('\nSENSITIVITY ANALYSIS:')
        myPrint('Maximum variation achieved (%), Couplings Real part:')
        for m in range(maxModReal.shape[0]):
            for n in range(maxModReal.shape[1]):
                if n==m: continue
                if maxModReal[m, n] > 0: myPrint('(%3s,%3s): %6.3g%%' % (rowIndices[m], rowIndices[n], maxModReal[m, n]))
        
        myPrint('Maximum variation achieved (%), Couplings Imaginary part:')
        for m in range(maxModImag.shape[0]):
            for n in range(maxModImag.shape[1]):
                if n==m: continue
                if maxModImag[m, n] > 0: myPrint('(%3s,%3s): %6.3g%%' % (rowIndices[m], rowIndices[n], maxModImag[m, n]))
                
        myPrint('Maximum variation achieved (%), Resonators:')
        for m in range(maxModQn.shape[0]):
            myPrint( 'R%d: fres %6.3g%%, Q %6.3g%%' % (m+1,  maxModF0[m],  maxModQn[m]) )
            
        app.restoreOverrideCursor()
        self.plotSensitivity()
        
    
    def randomVariations(self, MatQ, inout, inductive, capacitive,  resistive, f0var, Qvar):
        """
        Returns a randomly modified coupling matrix with maximum element variations determined by function arguments. The random variation has uniform distribution.
        @param MatQ = Coupling matrix to which random variations are applied (MatrixQ class instance). 
                      It is not modified, and the modified (deep) copy is returned.
        @param inout = Maximum random variation in % of nominal value, for input/output (source/load) couplings. It is applied to elements in first and last rows and columns.
        @param inductive = Maximum random variation in % of nominal value, for inductive couplings. It is applied to positive real parts of out-of-diagonal matrix elements \f$ \Re M_{ij} > 0 \qquad i \neq j \f$ .
        @param capacitive = Maximum random variation in % of nominal value, for capacitive couplings. It is applied to negative real parts of out-of-diagonal matrix elements \f$ \Re M_{ij} < 0 \qquad i \neq j \f$ .
        @param resistive = Maximum random variation in % of nominal value, for resistive couplings. It is applied to imaginary parts of out-of-diagonal matrix elements \f$ \Im M_{ij} > 0 \qquad i \neq j \f$ .
        @param f0var = Maximum random variation in % of nominal value, for resonators resonant frequency.
        @param Qvar = Maximum random variation in % of nominal value, for resonators quality factor.
        @return modMatQ = Modified coupling matrix, based on a deep copy of the MatQ argument.
        
        In order to obtain the randomly modified resonant frequency, the real part of diagonal elements corresponding to resonating nodes is unnormalized.
        The random variation is applied to the unnormalized frequency, and it is normalized again to obtain the modified real part of the matrix element.
        
        Diagonal elements corresponding to non-resonant nodes are not modified.
        
        The modified Q of resonators is obtained applying a random variation to the resonator Q of the original matrix (before applying the random variation to resistive couplings).
        Then, the resistive couplings are randomly modified and the new imaginary part of the diagonal element is computed accounting for the new values of the resistive couplings.
        
       In general, for each node \f$ n \f$, the quality factor is 
        \f[
             Q_{n} = \frac{1}{G_n \, \mathrm{FBW}} 
         \f]
        where the loss conductance of the unloaded resonator is:
        \f[
            G_{n} = - \sum_{m=1}^{N} \Im M_{mn}
        \f]
        
        The losses corresponding to the new \f$ Q'_n \f$ for resonator \f$ n \f$ are:
        \f[
            G'_n = \frac{1}{Q_n' \, \mathrm{FBW}}
        \f]
        But, after the random variation of resistive couplings the losses that we have are         
        \f[
            G''_n = - \sum_{m=1}^N \Im M''_{nm}
        \f]
        so, in order to achieve a quality factor \f$ Q' \f$, the modified value of the imaginary part of the diagonal element must be:
        \f[
            \Im M'_{nn} = \Im M''_{nn} + G''_n - G'_n
        \f]
        """
        rand = lambda x: 1 + (2*np.random.rand(1)[0]-1)*x/100       # Random number with uniform distribution in [-1-x/100, 1+x/100]
        
        # Threshold to consider the real or imaginary parts of a matrix element positive, negative or non-zero
#        eps = MatQ.parent.matrixElementsEps('Sensitivity analysis')      
        eps = 10** -self.mainWindow.CM_prec.value()     # Keep matrix topology in sync which what is displayed in the matrix table
        
        modMatQ = MatQ.copy()
        FBW = MatQ.FT.FBW       # Fractional bandwidth
        
        M = modMatQ.M
        N = M.shape[0]
        first = MatQ.extraNodesS        # Index of first  resonant node
        last = N - MatQ.extraNodesL    # Index of last  resonant node + 1
        
        # In the code below, varRe and varIm are the maximum random variation of real and imag parts of M[m,n]
        for m in range(N):
            for n in range(m+1,  N):
                #if n==m: continue   # Main diagonal will be computed later, after we have computed the variations of resistive couplings
                if m==0 or m==N-1 or n==0 or n==N-1:    # Input and output couplings (source and load)
                    if abs(M[m, n]) > eps: varRe, varIm = inout, inout 
                    else:                           varRe, varIm = 0, 0
                else:   # Couplings between resonators
                    if    M[m, n].real >  eps: varRe = inductive 
                    elif M[m, n].real < -eps: varRe = capacitive
                    else: varRe, varIm = 0, 0
                    
                    if abs(M[m, n].imag) > eps: varIm = resistive 
                    else:                                    varIm = 0
                    
                M[m, n] = complex(M[m, n].real * rand(varRe), M[m, n].imag * rand(varIm))
                M[n, m] = M[m, n]
            
        for n in range(first,  last):
            # Variation in Q
            Qn = MatQ.Q[n-first]           # Original Qn (before modifing resisitve couplings)
            modQn = Qn * rand(Qvar)   # Modified Q after random variation
            losses = M[:,n].imag.sum()   # Losses, accounting the variations in resistive couplings
            modLosses = -1/(FBW * modQn)    # Desired losses, according to modified Q
            imDiag = MatQ.M[n, n].imag + modLosses - losses     # New imaginary part for desired losses
            
            # Variation in f0
            f0n = MatQ.FT.unormFreq(M[n, n].real)   # Resonator frequency
            modf0n = f0n * rand(f0var)
            reDiag = MatQ.FT.normFreq(modf0n)

            M[n, n] = complex(reDiag, imDiag)
            
        modMatQ.Qresonators()
        return modMatQ
    
    
    def plotSensitivity(self):
        """
        Plot [S] parameters magnitude and phase graph, if they are available.
        The plotted data is self.S11, self.S21 and self.S22, which contain the [S] parameters for all random realizations, in columns.
        """
        
        SPlotSensitivity = self.mainWindow.mainWindow.SPlotSensitivity
        
        if  SPlotSensitivity is None or SPlotSensitivity.closed is True:
            
            # Set dynamic range to the lowest value of S21 at sweep range ends.
            dynamicRange = -20*log10( min(np.concatenate( (abs(self.S21M[0, :]), abs(self.S21M[-1, :] )) )) )
            dynamicRange = 10*ceil(dynamicRange/10)
            
            SPlotSensitivity = DbPlot( parent=self.mainWindow,  
                                                            windowTitle = 'Sensitivity analysis results', 
                                                            tabTitle = '[S] + Group delay', 
                                                            title = '[S] parameters (from Characteristic Polynomials)',                                                                     
                                                            XData = self.SP.freq / 1e9, 
                                                            leftYData = [20*np.log10(np.abs(self.S11M)),  20*np.log10(np.abs(self.S21M)),  20*np.log10(np.abs(self.S22M))], 
                                                            rightXData = self.SP.freq2 / 1e9,
                                                            rightYData = [ 1e9 * self.groupDelayM ],
                                                            leftYmin = -dynamicRange, 
                                                            leftYmax = 0, 
                                                            leftYNames = ['S<sub>11</sub>',  'S<sub>21</sub>',  'S<sub>22</sub>'],  
                                                            leftYVisible = [True,  True,  False], 
                                                            XLabel = 'Frequency (GHz)', 
                                                            leftYLabel = 'dB', 
                                                            Xunits = 'GHz',  leftYunits = 'dB', rightYunits = 'ns', 
                                                            rightYLabel = 'Time (ns.)' , 
                                                            rightYNames = ['Group delay'], 
                                                            rightYVisible = [True]
                                                            )
            SPlotSensitivity.addTab(  tabTitle = '[S] + Phase', 
                                                            title = '[S] parameters (from Characteristic Polynomials)',                                                                     
                                                            XData = self.SP.freq / 1e9, 
                                                            leftYData = [20*np.log10(np.abs(self.S11M)),  20*np.log10(np.abs(self.S21M)),  20*np.log10(np.abs(self.S22M)) ], 
                                                            rightYData = [ self.phaseS21M * 180/np.pi, self.phaseS11M * 180/np.pi,  self.phaseS22M * 180/np.pi ],
                                                            leftYmin = -dynamicRange, 
                                                            leftYmax = 0, 
                                                            leftYNames = ['S<sub>11</sub>',  'S<sub>21</sub>',  'S<sub>22</sub>'], 
                                                            leftYVisible = [True,  True,  False], 
                                                            XLabel = 'Frequency (GHz)', 
                                                            leftYLabel = 'dB', 
                                                            Xunits = 'GHz',  leftYunits = 'dB', rightYunits = 'deg', 
                                                            rightYLabel = 'degrees' , 
                                                            rightYNames = ['Phase(S<sub>21</sub>)',  'Phase(S<sub>11</sub>)',  'Phase(S<sub>22</sub>)'], 
                                                            rightYVisible = [True,  False,  False]
                                                            )
            SPlotSensitivity.addTab(  tabTitle = 'Group delay + Phase', 
                                                            title = '[S] parameters (from Characteristic Polynomials)',                                                                     
                                                            XData = self.SP.freq2 / 1e9, 
                                                            leftYData = [1e9 * self.groupDelayM], 
                                                            rightXData = self.SP.freq / 1e9,
                                                            rightYData = [ self.phaseS21M * 180/np.pi ],
                                                            leftYNames = ['Group delay'], 
                                                            XLabel = 'Frequency (GHz)', 
                                                            leftYLabel = 'Time (ns.)', 
                                                            Xunits = 'GHz',  leftYunits = 'ns', rightYunits = 'deg',
                                                            rightYLabel = 'degrees' , 
                                                            rightYNames = ['Phase(S<sub>21</sub>)'], 
                                                            rightYVisible = [True]
                                                            )
                                                            
            self.mainWindow.mainWindow.SPlotSensitivity = SPlotSensitivity
                                                            
        else:
            SPlotSensitivity.update(0,  self.SP.freq / 1e9,  [20*np.log10(np.abs(self.S11M)),  20*np.log10(np.abs(self.S21M)),  20*np.log10(np.abs(self.S22M))],  
                                                                    rightXData = self.SP.freq2 / 1e9,  rightYData=[ 1e9 * self.groupDelayM], autoScaleBottomX = self.mainWindow.mainWindow.autoScaleFreq )
                                                                    
            SPlotSensitivity.update(1,  self.SP.freq / 1e9,  [20*np.log10(np.abs(self.S11M)),  20*np.log10(np.abs(self.S21M)),  20*np.log10(np.abs(self.S22M))],   
                                                                    rightYData=[ self.phaseS21M * 180/np.pi,  self.phaseS11M * 180/np.pi,  self.phaseS22M * 180/np.pi], autoScaleBottomX = self.mainWindow.mainWindow.autoScaleFreq )
                                                                    
            SPlotSensitivity.update(2,  self.SP.freq2 / 1e9,  [1e9 * self.groupDelayM], rightXData = self.SP.freq / 1e9,  rightYData=[ self.phaseS21M * 180/np.pi ], autoScaleBottomX = self.mainWindow.mainWindow.autoScaleFreq )

            self.mainWindow.mainWindow.autoScaleFreq = False
            
        # Plot masks, or update if there are any
        if self.mainWindow.mainWindow.SM is not None: 
            SPlotSensitivity.deleteMasks()
            self.mainWindow.mainWindow.plotMasks()             

# End class: SensitivityAnalysis
#!!!!!!!!!!!!!!!!!!!!!!!!!#
        

#¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡#
# Begin class: CouplingMatrixDlg #

class CouplingMatrixDlg(QMainWindow, Ui_couplingmatrixdlg.Ui_CouplingMatrixDlg):
    """
    GUI dialog to show coupling matrices
    """

    def __init__(self, CM, parent=None):
        """
        Constructor: Creates dialog window with a table to display coupling matrices and buttons to save or store to disk, select matrix from list, plot [S] parameters or edit current matrix.
            @param CM = CouplingMatrix class instance.
            @param parent = Parent widget, in this case is mainWindow.
        """
        super(CouplingMatrixDlg, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)      # Necessary for Modless windows.
        
        ##
        # Copy of the mainWindow class instance.
        # It is necessary  to store a copy since it must be accessed by some member functions.
        self.mainWindow = parent
        self.setupUi(self)
        
        ##
        # Matrix edit dialog. It will be a Modeless Live dialog, that is only hidden when closed.
        self.matrixEditDlg = None
        
        ##
        # Sensitivity analysis dialog. It will be a Modeless Live dialog, that is only hidden when closed.
        self.sensitivityDlg = None
        
        ##
        # Shallow copy of the mainWindow CM class instance, for easier access.
        self.CM = CM
        
        ##
        # Attribute that is set to True when CM.MatQ  has been modified by user edit actions and not saved in list.
        self.dirty = False
        self.setWindowModified(self.dirty)
        
        ##
        # Attribute that is set to True when changes to matrix must be recorded. 
        # It is disabled, for example, when the user does and incorrect matrix edit and the program automatically restores the old matrix value.
        self.recordChanges = True
        
        ##
        # Attribute that is set to True before automatic calls to on_action_CM_comboBox_currentIndexChanged() to avoid loading a matrix.
        self.noLoadCM = False
        
        # Add CM selection comboBox to toolBar
        self.recordChanges = False  # Do not record automatic calls to on_CMcomboBox_currentIndexChanged()
        self.CM_comboBox = QComboBox(self.toolBar_2)
        self.CM_comboBox.setObjectName("CM_comboBox")
        self.CM_comboBox.setToolTip("Select matrix from list")
        self.CM_comboBox.setStatusTip("Select matrix from list")
        self.connect(self.CM_comboBox,  SIGNAL("currentIndexChanged(int)"),  self.on_CM_comboBox_currentIndexChanged)
        self.toolBar_2.insertWidget(self.action_ListRemove, self.CM_comboBox)
        
        # Low pass and Q Tab
        tabLPQ = QWidget()
        self.tabWidget.addTab(tabLPQ, "Low-pass matrix and Q")              
        
        # Horizontal layout that contains a vertical stack with matrix name, matrix table and Q table
        self.horizontalLayout_3 = QHBoxLayout(tabLPQ)
        self.horizontalLayout_3.addStretch()
        
        # Vertical layout with matrix name, matrix table and Q table
        self.verticalLayout_CM = QVBoxLayout()

        # Matrix name, with horizontal layout
        self.horizontalLayout_4 = QHBoxLayout()
        
        # Create and Matrix Name
        self.CM_nameLabel = QLabel('Coupling matrix name:')
        self.horizontalLayout_4.addWidget(self.CM_nameLabel)
        self.CM_name = QLineEdit('')
        self.horizontalLayout_4.addWidget(self.CM_name)
        self.horizontalLayout_4.addStretch()
        self.CM_prec_label = QLabel('Precision:')
        self.horizontalLayout_4.addWidget(self.CM_prec_label)
        self.CM_prec = QSpinBox()
        self.CM_prec.setRange(3, 14)
        
        prec = self.mainWindow.CM.matrixElementsEps('Display Coupling Matrix')
#        self.CM_prec.setValue(-int ( round(log10(prec)) ))
        self.CM_prec.setValue(4)    # CE prefers to see less significant digits by default.
        
        self.connect(self.CM_prec, SIGNAL("valueChanged(int)"), lambda x: self.loadMatrixQ(CM.MatQ) )
        self.horizontalLayout_4.addWidget(self.CM_prec)
        self.verticalLayout_CM.addLayout(self.horizontalLayout_4)
        
        # Matrix table
        self.CM_tableWidget = myTableWidget(name = "Coupling Matrix",  parent = self.centralwidget)
        self.CM_tooltip = "To manually edit coupling matrix elements,\nfirst open matrix editor by clicking the toolbar button."
        self.CM_tableWidget.setToolTip(QApplication.translate("CouplingMatrixDlg", self.CM_tooltip, None, QApplication.UnicodeUTF8))
        self.verticalLayout_CM.addWidget(self.CM_tableWidget)
        
        # Q table, with horizontal layout
        self.horizontalLayout_2 = QHBoxLayout()
        self.horizontalLayout_2.addStretch()
        self.Q_tableWidget = myTableWidget(name = "Resonators Q",  parent = self.centralwidget)  
        self.Q_tooltip = "To manually edit Q vector elements,\nfirst open matrix editor by clicking the toolbar button."
        self.Q_tableWidget.setToolTip(QApplication.translate("CouplingMatrixDlg", self.Q_tooltip, None, QApplication.UnicodeUTF8))
        self.horizontalLayout_2.addWidget(self.Q_tableWidget)    
        self.Qprec_Layout = QHBoxLayout()        
        self.Q_prec_label = QLabel('Precision:')
        self.Qprec_Layout.addWidget(self.Q_prec_label)
        self.Q_prec = QSpinBox()
        self.Q_prec.setRange(3, 14)
        self.Q_prec.setValue(5)
        self.connect(self.Q_prec, SIGNAL("valueChanged(int)"), lambda x: self.loadMatrixQ(CM.MatQ) )
        self.Qprec_Layout.addWidget(self.Q_prec)
        self.horizontalLayout_2.addLayout(self.Qprec_Layout)        
        self.horizontalLayout_2.addStretch()   
        self.verticalLayout_CM.addLayout(self.horizontalLayout_2)
        
        self.verticalLayout_CM.addStretch()
        
        self.horizontalLayout_3.addLayout(self.verticalLayout_CM)
        
        self.horizontalLayout_3.addStretch(1)
        
        # End Low-pass and Q Tab
        
        # Band-pass Tab
        tabBP = QWidget()
        self.tabWidget.addTab(tabBP, "Coupling bandwidth matrix (MHz)")              
        
        # Horizontal layout that contains a vertical stack with matrix name and matrix table
        self.horizontalLayout_3bp = QHBoxLayout(tabBP)
        self.horizontalLayout_3bp.addStretch()
        
        # Vertical layout with matrix name, matrix table and Q table
        self.verticalLayout_CMbp = QVBoxLayout()

        # Matrix name, with horizontal layout
        self.horizontalLayout_4bp = QHBoxLayout()
        
        # Create and Matrix Name
        self.CMbp_nameLabel = QLabel('Coupling matrix name:')
        self.horizontalLayout_4bp.addWidget(self.CMbp_nameLabel)
        self.CMbp_name = QLabel('')
        self.horizontalLayout_4bp.addWidget(self.CMbp_name)
        self.horizontalLayout_4bp.addStretch()
        self.CMbp_prec_label = QLabel('Precision:')
        self.horizontalLayout_4bp.addWidget(self.CMbp_prec_label)
        self.CMbp_prec = QSpinBox()
        self.CMbp_prec.setRange(3, 14)
        self.CMbp_prec.setValue(5)
        self.connect(self.CMbp_prec, SIGNAL("valueChanged(int)"), lambda x: self.loadMatrixQ(CM.MatQ) )
        self.horizontalLayout_4bp.addWidget(self.CMbp_prec)
        self.verticalLayout_CMbp.addLayout(self.horizontalLayout_4bp)
        
        # Matrix table
        self.CMbp_tableWidget = myTableWidget(name = 'Coupling Bandwidth Matrix',  parent = self.centralwidget)
        self.CMbp_tooltip = "To manually edit coupling matrix elements,\nfirst open matrix editor by clicking the toolbar button."
        self.CMbp_tableWidget.setToolTip(QApplication.translate("CouplingMatrixDlg", self.CMbp_tooltip, None, QApplication.UnicodeUTF8))
        self.verticalLayout_CMbp.addWidget(self.CMbp_tableWidget)
        
        # Impedances label
        # Only for Jordi Mateu's version of bandpass matrix or bandwidth coupling matrix
        # Not relevant in CMSDemo version of bandpass matrix or bandwidth coupling matrix
#        self.verticalLayout_CMbp.addWidget(QLabel('Z<sub>0</sub> = Z<sub>S</sub> = Z<sub>L</sub> = 50 Ohm'))
        
        self.verticalLayout_CMbp.addStretch()
        
        self.horizontalLayout_3bp.addLayout(self.verticalLayout_CMbp)
        
        self.horizontalLayout_3bp.addStretch(1)
        
        # End Band-pass Tab
        
        # Widget properties
        self.CM_name.setReadOnly(True)
        
        # Format Matrix name as a frameless QLineEdit with bold font and background colour.
        self.CM_name.setFrame(False)
        font = self.CM_name.font()
        font.setBold(True)
        self.CM_name.setFont(font)
        palette = self.CM_name.palette()
        palette.setColor(QPalette.Base,  self.palette().window().color())
        self.CM_name.setPalette(palette)
        
        # Set bold font also for band-pass matrix name
        self.CMbp_name.setFont(font)
        
        self.CM_tableWidget.setColumnCount(0)
        self.CM_tableWidget.setRowCount(0)
        self.Q_tableWidget.setColumnCount(0)
        self.Q_tableWidget.setRowCount(0)
        self.CMbp_tableWidget.setColumnCount(0)
        self.CMbp_tableWidget.setRowCount(0)
        
        self.CM_tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
        self.Q_tableWidget.setSelectionMode(QAbstractItemView.NoSelection) 
        self.CMbp_tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
        
        # Layout policy and stretch
        self.CM_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum,  QSizePolicy.Maximum))
        self.Q_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum,  QSizePolicy.Maximum))
        self.CMbp_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum,  QSizePolicy.Maximum))
#        self.CM_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,  QSizePolicy.Preferred))
#        self.Q_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,  QSizePolicy.Preferred))
#        self.CMbp_tableWidget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum,  QSizePolicy.Preferred))
#        self.horizontalLayout_3.setStretchFactor(self.CM_tableWidget,  100)
#        self.horizontalLayout_2.setStretchFactor(self.Q_tableWidget,  100)
#        self.verticalLayout.setStretchFactor(self.CM_tableWidget,  100)
#        self.verticalLayout.setStretchFactor(self.Q_tableWidget,  100)

        # Connect table modified signal
        self.connect(self.CM_tableWidget,  SIGNAL("itemChanged(QTableWidgetItem*)"),  self.CM_itemChanged)
        self.connect(self.CM_tableWidget,  SIGNAL("currentCellChanged(int, int, int, int)"),  self.CM_currentCellChanged )
        self.connect(self.Q_tableWidget,  SIGNAL("itemChanged(QTableWidgetItem*)"),  self.Q_itemChanged)
        self.connect(self.Q_tableWidget,  SIGNAL("currentCellChanged(int, int, int, int)"),  self.Q_currentCellChanged )
        #self.connect(self.CM_name,  SIGNAL("textEdited(QString)"),  self.matrixQChanged)
        self.connect(self.CM_name,  SIGNAL("editingFinished()"),  self.matrixQChanged)
        
        # Add available matrices to CM_comboBox
        for MatQ in CM.listM:
            self.CM_comboBox.addItem(MatQ.name)
        
        # Set the current coupling matrix to the previous one        
        self.CM_comboBox.setCurrentIndex(self.CM.indexCM)
        
        # Set tables READ-ONLY
        self.CM_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.Q_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.CMbp_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        
        # Load coupling matrix  and Q tables
        self.loadMatrixQ(CM.MatQ)
        
        self.setWindowModified(False)
        
        # Start recording changes.
        self.recordChanges = True
        
        # Create matrix edit dialog to allow loading matrices from the coupling matrix window.
        self.matrixEditDlg = MatrixEditDlg(self)
        self.connect(self.CM_prec, SIGNAL("valueChanged(int)"), self.matrixEditDlg.udpateTopology)
        
        ##
        # SensitivityAnalisys class instance.
        self.SA = SensitivityAnalysis(self)


    def closeEvent(self,  event):
        """
        Reimplementation of the window close function.
        Sets mainWindow.CMdialog to None.
        """
        # Check if matrix has been modified and not saved
        if not self.okToContinue():    
            event.ignore()
        else:
            self.mainWindow.CMdialog = None
            
            # Close [S] error plot if it exists
            if self.mainWindow.SPlotError is not None and not self.mainWindow.SPlotError.closed:
                self.mainWindow.SPlotError.close()
                self.mainWindow.SPlotError = None
                
            # Close energy plot if it exists
            if self.mainWindow.EnergyPlot is not None and not self.mainWindow.EnergyPlot.closed:
                self.mainWindow.EnergyPlot.close()
                self.mainWindow.EnergyPlot = None

            # Close sensitivity plot if it exists
            if self.mainWindow.SPlotSensitivity is not None and not self.mainWindow.SPlotSensitivity.closed:
                self.mainWindow.SPlotSensitivity.close()
                self.mainWindow.SPlotSensitivity = None
            
            event.accept()
            
            
    def loadMatrixQ(self,  MatQ,  loadMatrix=True,  loadQ=True):
        """
        Load a N+2 coupling matrix into a QTableWidget.
        All actions that modify the coupling matrix, end up executing this method, so the [S] error plot is updated if it exists.
        @param MatQ = MatrixQ class instance.
        @param loadMatrix = Load contents of coupling matrix. Default True.
        @param loadQ = Load contents of Q vector. Default True.
        """
        
        # Compute Coupling Bandwidth version of current matrix
        MatQ.lp2bp2cbw()
        
        if loadMatrix:
            
            # Load matrix name
            #self.CM_name.setText('<b>' + MatQ.name + '</b>')
            self.CM_name.setText(MatQ.name)
            self.CMbp_name.setText(MatQ.name)
            
            # Load low-pass M table
            table = self.CM_tableWidget
            table.clear()
            M = MatQ.M
            size = M.shape[0]
            first = MatQ.extraNodesS
            last = size - MatQ.extraNodesL
            
            table.clear()
            table.setColumnCount(size)
            table.setRowCount(size)
            
            # Row and column header labels
            labels = ['S']
            for n in range(1, first ): labels.append('NR' + str(n))
            labels.extend(  [ 'R'+str(elem) for elem in range(1,  last-first+1) ] )
            for n in range(first,  first+MatQ.extraNodesL-1 ): labels.append('NR' + str(n))
            labels.append('L')
            table.setHorizontalHeaderLabels( labels )
            table.setVerticalHeaderLabels( labels )
            self.rowIndices = labels    # Will be used in matrix rotation pivot indices comboBox
            MatQ.rowIndices = labels    # Will be used for saving energy and power
            
            for m in range(size):
                for n in range(size):
                    item = QTableWidgetItem( complexStr(M[m, n],  self.CM_prec.value()) ) 
                    item.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    table.setItem(m, n, item)
                    
            table.resizeRowsToContents()
            table.resizeColumnsToContents()
            table.setMaximumSize( table.maximumSizeHint() )

            # Load band-pass M table
            table = self.CMbp_tableWidget
            table.clear()
            Mcbw = MatQ.Mcbw
            
            table.clear()
            table.setColumnCount(size)
            table.setRowCount(size)
            
            # Row and column header labels
            table.setHorizontalHeaderLabels( labels )
            table.setVerticalHeaderLabels( labels )
            
            for m in range(size):
                for n in range(size):
                    item = QTableWidgetItem( complexStr(Mcbw[m, n],  self.CMbp_prec.value()) ) 
                    item.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    table.setItem(m, n, item)
                    
            table.resizeRowsToContents()
            table.resizeColumnsToContents()
            table.setMaximumSize( table.maximumSizeHint() )
            
            
            # Update matrixEditDlg
            if self.matrixEditDlg is not None: 
                self.matrixEditDlg.setMatrixSize()  # Must be called after setting self.rowIndices
                self.matrixEditDlg.udpateTopology()
                self.matrixEditDlg.Q_doubleSpinBox.setValue( np.mean(np.abs(MatQ.Q)) )

        if loadQ:
            # Load Q table
            Q = MatQ.Q
            N = Q.shape[0] # Number of resonators
    
            table = self.Q_tableWidget
            table.clear()
            table.setColumnCount(N)
            table.setRowCount(1)
            table.setHorizontalHeaderLabels( [ str(elem) for elem in range(1, N+1) ])
            table.setVerticalHeaderLabels( [ 'Q' ] )
            
            for n in range(N):
                item = QTableWidgetItem( '%.*g' % (self.Q_prec.value(),  Q[n]) ) 
                item.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                table.setItem(0, n, item)
                    
            table.resizeRowsToContents()
            table.resizeColumnsToContents()
            table.setMaximumSize( table.maximumSizeHint() )
            
        # Update [S] parameters plot, if it exists
        if self.mainWindow.SPlotError is not None and (self.mainWindow.SPlotError.closed is False):
            self.on_action_PlotS_triggered(True)
            
        # Update energy plot, if it exists
        if self.mainWindow.EnergyPlot is not None and (self.mainWindow.EnergyPlot.closed is False):
            self.on_action_PlotEnergy_triggered(True)
            
            
    def okToContinue(self):
        """
        This function checks if matrix has been modified and not saved to list. If it has been modified, asks the user to save the matrix.
        The function is executed by the GUI when there is an action that will destroy CM.MatQ: Load matrix from list or read matrix from disk.
            @return ok (True / False) = If True the use has agreed to continue, if False the user cancels the operation.
        """
        if self.dirty:
            message = "Coupling Matrix has been modified, but not saved to list\nSave?"
            reply = QMessageBox.question(self, "%s - Unsaved Changes" % applicationName, message, QMessageBox.Yes | QMessageBox.No| QMessageBox.Cancel)
            if reply == QMessageBox.Cancel:
                return False
            elif reply == QMessageBox.Yes:
                self.on_action_ListAdd_triggered(True,  programmed=True)
        return True
        
    ##@{
    # @name  Functions automatically excuted when the user interacts with the GUI 
    
    
    def CM_currentCellChanged(self, m, n,  mold,  nold):
        """
        Slot that is executed whenever current CM cell is changed
        """
        if self.action_Edit.isChecked() is False: return    # Do nothing if we not in edit mode
        
        table = self.CM_tableWidget
        item = table.currentItem()  # table.item(m, n)
        if item is None: return
        
        self.recordChanges = False
        st = complexStr(self.CM.MatQ.M[m, n],  14)
        item.setText(st)

        itemOld = table.item(mold, nold) 
        stOld = complexStr(self.CM.MatQ.M[mold, nold],  self.CM_prec.value())
        if itemOld is not None: itemOld.setText(stOld)
        
        self.recordChanges = True
        
        table.resizeRowsToContents()
        table.resizeColumnsToContents()
        table.setMaximumSize( table.maximumSizeHint() )
        
    
    def Q_currentCellChanged(self, m, n,  mold,  nold):
        """
        Slot that is executed whenever current Q cell is changed
        """
        if self.action_Edit.isChecked() is False: return    # Do nothing if not in edit mode
        
        table = self.Q_tableWidget
        item = table.currentItem()  
        if item is None: return
        
        self.recordChanges = False
        st = complexStr(self.CM.MatQ.Q[n],  14)
        item.setText(st)

#        # for some reason, itemOld is not the last item, but the one-before-last, but (mold, nold) are correct anyway
#        itemOld = table.item(mold, nold)
#        stOld = complexStr(self.CM.MatQ.M[mold, nold],  self.CM_prec.value())
#        if itemOld is not None: itemOld.setText(stOld)
        
        self.recordChanges = True
        
        table.resizeRowsToContents()
        table.resizeColumnsToContents()
        table.setMaximumSize( table.maximumSizeHint() )
    
    
    def matrixQChanged(self):
        """
        Slot that is executed whenever CM.MatQ is changed by a matrix edit action.
        The self.dirty attribute is set to True.
        """
        MatQ = self.CM.MatQ
        
        if self.sender() is self.CM_name:
            st = unicode(self.CM_name.text())
            if st != MatQ.name:
                MatQ.name = st
                MatQ.fileExt = None
                self.matrixEditDlg.lastEditAction = 'Matrix name changed to: %s' % st
            else: # Nothing has been modified here.
                return
        
        self.dirty = True
        self.setWindowModified(self.dirty)
        
        # Update Q
        self.recordChanges = False
        MatQ.Qresonators()
        self.loadMatrixQ(MatQ,  loadMatrix=False)
        self.recordChanges = True
        
        self.pushEditAction()
        
        
    def CM_itemChanged(self,  item):
        """
        Update the contents of self.CM.MatQ after the user has edited the CM table widget.
        """
        # Do nothing is recording changes is disabled.
        if self.recordChanges == False: return
        
        row = self.CM_tableWidget.currentRow()
        col = self.CM_tableWidget.currentColumn()
        M = self.CM.MatQ.M
        
        if row>=0 and col>=0:
            st = str(item.text().trimmed())
            
            # Python complex() function needs 'j' imaginary unit.
            if st.find('i') != -1:
                st = st.replace('i','j') 
                # Update GUI table with complex number using 'j' instead of 'i'.
                self.recordChanges = False      # Disable recording this matrix change
                item.setText(st)   
                self.recordChanges = True       # Re-enable recording.
            
            try:
                value = complex(st)
            except ValueError,  err:
                QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect value error. String:<br>%s<br>is not a correct complex number representation.</p>' % (st))
                
                # Put back the old value in last edited cell
                self.recordChanges = False      # Disable recording this matrix change
                item.setText(complexStr(M[row, col],  4))   
                self.recordChanges = True       # Re-enable recording.
#@todo Set the selected cell back to the cell that was erroneously edited. The following code does not work as it is expected to.
#                # Select back last edited cell
#                self.CM_tableWidget.setCurrentCell(row,  col)   
#                self.CM_tableWidget.setRangeSelected (QTableWidgetSelectionRange(row,  col,  row,  col),  True)
#                item.setSelected(True)
#                self.CM_tableWidget.setCurrentItem(item)
#                self.CM_tableWidget.scrollToItem(item)

            else:
                #print "(%d,%d) = %s = (%g,%g)" % (row,  col,  st,  value.real,  value.imag)
                M[row,  col] = value
                if row == col:
                    self.matrixEditDlg.lastEditAction = 'CM Matrix entry edited: (%d,%d) = %s' % (row,  col,  st)
                else:            # For non diagonal elements, change also the reciprocal.
                    M[col, row] = value
                    itemSym = QTableWidgetItem(st)
                    itemSym.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    self.recordChanges = False      # Disable recording this matrix change
                    self.CM_tableWidget.setItem(col, row, itemSym)   
                    self.recordChanges = True       # Re-enable recording.
                    self.matrixEditDlg.lastEditAction = 'CM Matrix entry edited: (%d,%d) and (%d,%d) = %s' % (row,  col,  col,  row,  st)
                self.matrixQChanged()
                
                # Update coupling bandwidth matrix
                self.CM.MatQ.lp2bp2cbw()  
                item = QTableWidgetItem( complexStr(self.CM.MatQ.Mcbw[row, col],  self.CMbp_prec.value()) ) 
                item.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                self.CMbp_tableWidget.setItem(row, col, item)
                if row != col: 
                    itemSym = QTableWidgetItem( complexStr(self.CM.MatQ.Mcbw[col, row],  self.CMbp_prec.value()) ) 
                    itemSym.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    self.CMbp_tableWidget.setItem(col, row, itemSym)
        
        
    def Q_itemChanged(self,  item):
        """
        Update the contents of self.CM.MatQ after the user has edited the Q table widget.
        
        For each node \f$ i \f$, the quality factor is 
        \f[
             Q_{i} = \frac{1}{G_i \, \mathrm{FBW}} 
         \f]
        where the loss conductance of the unloaded resonator is:
        \f[
            G_{i} = - \sum_{j=1}^{N} \Im M_{ji} = - \Im M_{ii} - \sum_{i \neq j} \Im M_{ji}
        \f]
        
        If we change the \f$ Q_i \f$ of a resonator, the new value of \f$ \Im M_{ii} \f$ is:
            \f[
                \Im M_{ii} = - G_{i} - \sum_{i \neq j} \Im M_{ji} = \frac{-1}{Q_{i} \, \mathrm{FBW}} - \sum_{i \neq j} \Im M_{ji}
            \f]
        """
        # Do nothing is recording changes is disabled.
        if self.recordChanges == False: return
        
        row = self.Q_tableWidget.currentRow()
        col = self.Q_tableWidget.currentColumn()
        MatQ = self.CM.MatQ
        M = MatQ.M
        Q = MatQ.Q
        
        if row>=0 and col>=0:
            st = str(item.text().trimmed())
            
            try:
                value = float(st)
            except ValueError,  err:
                QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect value error. String:<br>%s<br>is not a correct real number representation.</p>' % (st))
                
                # Put back the old value in last edited cell
                self.recordChanges = False      # Disable recording this matrix change
                item.setText('%.5g' % Q[col])   
                self.recordChanges = True       # Re-enable recording.
            else:
                #print "(%d,%d) = %s = %g" % (row,  col,  st,  value)
                #oldLosses  = -1 / (Q[col] * MatQ.FT.FBW)
                #newLosses = -1 / (value * MatQ.FT.FBW)
                #deltaM = newLosses - oldLosses
                Q[col] = value
                self.matrixEditDlg.lastEditAction = 'Q vector entry edited: (%d) = %s' % (col,  st)
                
                # Update diagonal entry of CM matrix
                colM = col + MatQ.extraNodesS
                losses = -1 / (value * MatQ.FT.FBW)
                sumImagColNoDiag = M[:, colM].imag.sum() - M[colM, colM].imag
                imDiag = losses - sumImagColNoDiag
                    
                M[colM, colM] = complex(M[colM, colM] .real, imDiag)
                itemM = QTableWidgetItem(complexStr(M[colM, colM],  4 ))
                itemM.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                self.recordChanges = False      # Disable recording this matrix change
                self.CM_tableWidget.setItem(colM, colM, itemM)   
                self.recordChanges = True       # Re-enable recording.
                self.matrixQChanged()


    @pyqtSignature("bool")
    def on_runSensitivity_pushButton_clicked(self,  checked):        
        """
        Run sensitivity analysis
        This function is automatically executed by the GUI when the user pushes the 'Run' button in the sensitivity groupBox.
        """
        inout = self.inout_doubleSpinBox.value()
        inductive = self.inductive_doubleSpinBox.value()
        capacitive = self.capacitive_doubleSpinBox.value()
        resistive = self.resistive_doubleSpinBox.value()
        freq = self.freq_doubleSpinBox.value()
        Q = self.Q_doubleSpinBox.value()
        N = self.N_spinBox.value()
        self.SA.runAnalysis(N, inout, freq,  Q, inductive, capacitive, resistive)


    @pyqtSignature("bool")
    def on_action_Edit_toggled(self,  checked):
        """
        Enable or disable matrix edit.
        This function is automatically executed by the GUI when the user toggles the 'Edit' button.
        """
        if checked: 
            self.CM_tableWidget.setEditTriggers(QAbstractItemView.AnyKeyPressed | QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
            self.CM_tableWidget.setSelectionMode(QAbstractItemView.SingleSelection)
            self.CM_tableWidget.setToolTip("")
            self.Q_tableWidget.setEditTriggers(QAbstractItemView.AnyKeyPressed | QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
            self.Q_tableWidget.setSelectionMode(QAbstractItemView.SingleSelection)
            self.Q_tableWidget.setToolTip("")
            self.CM_name.setReadOnly(False)
            self.CM_name.deselect()
            
            # Format Matrix name as default.
            self.CM_name.setFrame(True)
            font = self.CM_name.font()
            font.setBold(False)
            self.CM_name.setFont(font)
            palette = self.CM_name.palette()
            palette.setColor(QPalette.Base,  self.palette().base().color())
            self.CM_name.setPalette(palette)
            
            self.recordChanges = True

            self.matrixEditDlg.show()
        else:
            self.CM_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.CM_tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
            self.CM_tableWidget.setToolTip(QApplication.translate("CouplingMatrixDlg", self.CM_tooltip, None, QApplication.UnicodeUTF8))
            self.Q_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.Q_tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
            self.Q_tableWidget.setToolTip(QApplication.translate("CouplingMatrixDlg", self.Q_tooltip, None, QApplication.UnicodeUTF8))
            self.CM_name.setReadOnly(True)
            self.CM_name.deselect()
            
            # Format Matrix name as a frameless QLineEdit with bold font and background colour.
            self.CM_name.setFrame(False)
            font = self.CM_name.font()
            font.setBold(True)
            self.CM_name.setFont(font)
            palette = self.CM_name.palette()
            palette.setColor(QPalette.Base,  self.palette().window().color())
            self.CM_name.setPalette(palette)
        
            self.matrixEditDlg.close()
            
            # Update the matrix to set the precision after editing matrix entries
            self.loadMatrixQ(self.CM.MatQ)
            
            
    @pyqtSignature("bool")
    def on_sensitivity_groupBox_toggled(self,  checked):
        """
        Enable or disable sensitivity analysis.
        This function is automatically executed by the GUI when the user toggles the 'Sensitivity analysis' groupBox.
        """
        if checked: 
            pass
        else:
            pass
                        
            
    @pyqtSignature("bool")
    def on_action_Read_triggered(self,  checked):
        """
        Load current matrix from disk.
        """
        # Check if there are unsaved changes
        if not self.okToContinue():    
            return
        
        # Set open directory to directory of current file or to lastDir if there is no open file
        dir = self.mainWindow.P.outDir
        
        # Ask for file name
        formats = ["*.cm", "*.tcm", "*.fcm",  "*.fcm_uniQ", "*.pfi", "*.ina", "*.ins", "*.arrow", "*.cds", "*.triplet", "*.cqs"  ]
        fileName = unicode(QFileDialog.getOpenFileName(self, "%s - Open Coupling Matrix File" % applicationName, dir, "Coupling matrices files (%s)" % " ".join(formats)))
                    
        # If there is a file to open, read parameters. Otherwise (File Open dialog cancelled by user) do nothing
        if fileName: 
            try:
                self.CM.MatQ = self.CM.readCouplingMatrix(fileName)
            except parseError,  err:
                QMessageBox.critical(self,  "ERROR", '<p align="center">Error reading coupling matrix file:<br>%s</p>' % err)
                return False
            self.loadMatrixQ(self.CM.MatQ)
            self.matrixEditDlg.lastEditAction = "Matrix '%s' read from disk file '%s'" % (self.CM.MatQ.name, os.path.basename(fileName) )
            self.pushEditAction()
            

    @pyqtSignature("bool")
    def on_action_SaveAll_triggered(self, checked):
        """
        Save all matrices to disk, using default file names and extensions.
        """
        self.CM.saveCouplingMatrices(self.mainWindow.P, self.mainWindow.SP, saveSignificantDigits, saveSignificantDigitsEnergy)
    

    @pyqtSignature("bool")
    def on_action_Save_triggered(self,  checked):
        """
        Save current matrix in disk.
        """
        # Ask for file name
        formats = ["*.cm", "*.tcm", "*.fcm",  "*.fcm_uniQ", "*.pfi", "*.ina", "*.ins", "*.arrow", "*.cds", "*.triplet", "*.cqs" ]
        dir = self.mainWindow.P.outDir
        fileName = unicode(QFileDialog.getSaveFileName(self, "%s - Save Coupling Matrix File" % applicationName, dir, "Coupling matrices files (%s)" % " ".join(formats)))
        
        # If fileName is valid, save parameters. Otherwise (File Save dialog cancelled by user) do nothing
        if fileName:
            fileBaseName = os.path.basename(fileName)
            if "." not in fileBaseName:
                fileName += '.cm'
                # + self.CM.MatQ.fileExt

            self.CM.MatQ.saveMat(fileName,  self.mainWindow.P, self.mainWindow.SP, saveSignificantDigits, saveSignificantDigitsEnergy)
            myPrint("Coupling Matrix saved to file: %s" % fileName)


    @pyqtSignature("bool")
    def on_action_ListAdd_triggered(self,  checked,  programmed=False):
        """
        Store current matrix in self.CM_comboBox list.
        @param checked = SIGNAL/SLOT parameter.
        @param programmed = Flag that is true when this function is executed by the program, not the user, to avoid setting CM_comboBox CurrentIndex. Default False.
        """
        MatQ = self.CM.MatQ
        name = MatQ.name
        index = self.CM_comboBox.findText(name,  Qt.MatchExactly)
        
        if index >= 0:  # name exists in list
            message = "Matrix with same name already exists in list\nReplace?"
            reply = QMessageBox.question(self, "%s - Matrix exists" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.No:  # Exit without doing anything
                return False
            elif reply == QMessageBox.Yes:
                self.CM.listM[index] = MatQ.copy()
                myPrint("Coupling matrix '%s' replaced in list" % name)
                if self.matrixEditDlg is not None and self.recordChanges:
                    self.matrixEditDlg.lastEditAction = "Matrix '%s' replaced in list" % name
                    self.pushEditAction()
        else:   # Append matrix to list
            self.CM.listM.append(MatQ.copy())
            self.CM_comboBox.addItem(name)
            index = self.CM_comboBox.count() - 1
            self.CM.indexCM = index
            myPrint("Coupling matrix '%s' appended to list" % name)
            if self.matrixEditDlg is not None and self.recordChanges:
                self.matrixEditDlg.lastEditAction = "Matrix '%s' appended to list" % name
                self.pushEditAction()

        self.dirty = False
        self.setWindowModified(self.dirty)
        
        if programmed is False:
            # Call  setCurrentIndex AFTER setting self.dirty = False, because on_CM_comboBox_currentIndexChanged will be automatically and it calls self.okToContinue() .
            self.recordChanges = False
            self.CM_comboBox.setCurrentIndex(index) 
            self.recordChanges = True


    @pyqtSignature("bool")
    def on_action_ListRemove_triggered(self,  checked):
        """
        Remove current matrix from self.CM_comboBox and CM.listM lists.
        """
        if self.CM_comboBox.count() == 0: 
            QMessageBox.warning(self, "WARNING",  'Coupling matrices list is empty' )
            return
        
        index = self.CM_comboBox.currentIndex()
        name = str(self.CM_comboBox.currentText())
        
        message = "Are you sure you what to remove matrix '%s' from list?\nRemove?" % name
        reply = QMessageBox.question(self, "%s - Remove matrix" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            self.recordChanges = False # Disable changes to avoid CM_comboBox.removeItem re-loading the same matrix
            self.CM_comboBox.removeItem(index)
            del self.CM.listM[index]
            self.recordChanges = True
            myPrint("Coupling matrix '%s' removed from list" % name)
            
            if self.CM_comboBox.count() > 0: 
                # Load the matrix corresponding to the new index in CM_comboBox
                self.on_CM_comboBox_currentIndexChanged(self.CM_comboBox.currentIndex())
                self.CM.indexCM = self.CM_comboBox.currentIndex()
            
            
    @pyqtSignature("bool")
    def on_action_PlotEnergy_triggered(self,  checked):
        """
        Plot stored energy and dissipated power computed from current coupling matrix.
        """
        SP = self.mainWindow.SP
        SP.energyPowerFromCM(self.CM.MatQ)
        Nr = np.size(SP.energyRes,1)    # Number of resonators
        
        # Prepare a list of curves for potting
        energyCurvesList, energyRevCurvesList,  powerCurvesList, powerRevCurvesList, resonatorNames = [], [], [], [], []
        for n in range(Nr):
            energyCurvesList.append(1e9 * SP.energyRes[:, n])
            energyRevCurvesList.append(1e9 * SP.energyResRev[:, n])
            powerCurvesList.append(SP.powerRes[:, n])
            powerRevCurvesList.append(SP.powerResRev[:, n])
            resonatorNames.append('Resonator %d' % (n+1) )
            
        totalEnergyRes = 1e9 * SP.energyRes.sum(axis=1)
        totalEnergyResRev = 1e9 * SP.energyResRev.sum(axis=1)
        totalPowerRes = SP.powerRes.sum(axis=1)
        totalPowerResRev = SP.powerResRev.sum(axis=1)
        energyCurvesList.append(totalEnergyRes)
        energyRevCurvesList.append(totalEnergyResRev)
        powerCurvesList.append(totalPowerRes)
        powerRevCurvesList.append(totalPowerResRev)
        resonatorNames.append('Total')
        
        # Threshold to consider the real or imaginary parts of a matrix element positive, negative or non-zero
        eps = 10** -self.CM_prec.value()     # Keep matrix topology in sync which what is displayed in the matrix table
        
        powerCoupCurves, powerCoupRevCurves = [], []
        powerCoupNames = []
        powerCoupTotal = np.zeros(SP.freq.shape)
        powerCoupRevTotal = 0
        for coup in SP.powerCoup:
            if np.abs(coup[1]) < eps: continue      # Filter couplings with Gmn smaller than matrix precision
            (m, n) = coup[0]
            powerCoupTotal += coup[2]
            powerCoupRevTotal += coup[3]
            powerCoupCurves.append(coup[2])
            powerCoupRevCurves.append(coup[3])
            powerCoupNames.append('(%s,%s) = %.3g' % (self.rowIndices[m], self.rowIndices[n], coup[1] ))
        
        powerCoupCurves.append(powerCoupTotal)
        powerCoupRevCurves.append(powerCoupRevTotal)
        powerCoupNames.append('Total')
        
#        powerCoupCurves.append(totalPowerRes)
#        powerCoupRevCurves.append(totalPowerResRev)
#        powerCoupNames.append('Total at resonators')
            
#        energyCoupCurves, energyCoupRevCurves = [], []
#        energyCoupNames = []
#        for coup in SP.energyCoup:
#            if np.abs(coup[1]) < eps: continue      # Filter couplings with Bmn smaller than matrix precision
#            (m, n) = coup[0]
#            energyCoupCurves.append(1e9*coup[2])
#            energyCoupRevCurves.append(1e9*coup[3])
#            energyCoupNames.append('(%s,%s) = %.3g' % (self.rowIndices[m], self.rowIndices[n], coup[1] ))
        
        # Check if an EnergyPlot plot exists to create a new one or to replace existing data.
        if self.mainWindow.EnergyPlot is None or self.mainWindow.EnergyPlot.closed: 
            # Now that there is no open EnergyPlot window, create a new one.
            self.mainWindow.EnergyPlot = EnergyDbPlot(parent=self,  
                                                                    windowTitle = 'Stored Energy and dissipated power', 
                                                                    tabTitle = 'Energy', 
                                                                    title = 'Stored energy in the resonators computed from CM',
                                                                    XData = SP.freq / 1e9, 
                                                                    leftYData = energyCurvesList, 
                                                                    leftYNames = resonatorNames, 
                                                                    XLabel = 'Frequency (GHz)', 
                                                                    leftYLabel = 'nJ',             
                                                                    Xunits = 'GHz',  leftYunits = 'nJ',
                                                                    )
            self.mainWindow.EnergyPlot.appendReverseData(leftYDataRev = energyRevCurvesList, rightYDataRev = None)
                                                                    
            self.mainWindow.EnergyPlot.addTab(tabTitle = 'Power', 
                                                                    title = 'Dissipated power in the resonators computed from CM',
                                                                    XData = SP.freq / 1e9, 
                                                                    leftYData = powerCurvesList, 
                                                                    leftYNames = resonatorNames, 
                                                                    XLabel = 'Frequency (GHz)', 
                                                                    leftYLabel = 'W',             
                                                                    Xunits = 'GHz',  leftYunits = 'W',
                                                                    )
            self.mainWindow.EnergyPlot.appendReverseData(leftYDataRev = powerRevCurvesList, rightYDataRev = None)
            
            self.mainWindow.EnergyPlot.addTab(  tabTitle = 'Couplings power', 
                                                                        title = 'Dissipated power in the couplings computed from CM',
                                                                        XData = SP.freq / 1e9, 
                                                                        leftYData = powerCoupCurves, 
                                                                        leftYNames = powerCoupNames, 
                                                                        XLabel = 'Frequency (GHz)', 
                                                                        leftYLabel = 'W',             
                                                                        Xunits = 'GHz',  leftYunits = 'W',
                                                                     )
            self.mainWindow.EnergyPlot.appendReverseData(leftYDataRev = powerCoupRevCurves, rightYDataRev = None)
            
        else:
            self.mainWindow.EnergyPlot.addTab(  XData = SP.freq / 1e9, 
                                                                        leftYData = energyCurvesList, 
                                                                        leftYNames = resonatorNames, 
                                                                       replaceTab = 0 )
            self.mainWindow.EnergyPlot.replaceReverseData(0,  leftYDataRev = energyRevCurvesList, rightYDataRev = None)
                                                                       
            self.mainWindow.EnergyPlot.addTab(  XData = SP.freq / 1e9, 
                                                                        leftYData = powerCurvesList, 
                                                                        leftYNames = resonatorNames, 
                                                                       replaceTab = 1 )
            self.mainWindow.EnergyPlot.replaceReverseData(1,  leftYDataRev = powerRevCurvesList, rightYDataRev = None)
                                                                       
            self.mainWindow.EnergyPlot.addTab(  XData = SP.freq / 1e9, 
                                                                        leftYData = powerCoupCurves, 
                                                                        leftYNames = powerCoupNames, 
                                                                       replaceTab = 2 )
            self.mainWindow.EnergyPlot.replaceReverseData(2, leftYDataRev = powerCoupRevCurves, rightYDataRev = None)
            
            
#        self.mainWindow.EnergyPlot.addTab(  tabTitle = 'Couplings energy', 
#                                                                    title = 'Stored energy (W<sub>m</sub> - W<sub>e</sub>) at magnetic or electric couplings computed from CM',
#                                                                    XData = SP.freq / 1e9, 
#                                                                    leftYData = energyCoupCurves, 
#                                                                    leftYNames = energyCoupNames, 
#                                                                    XLabel = 'Frequency (GHz)', 
#                                                                    leftYLabel = 'nJ',             
#                                                                    Xunits = 'GHz',  leftYunits = 'nJ',
#                                                                 )
#        self.mainWindow.EnergyPlot.appendReverseData(leftYDataRev = energyCoupRevCurves, rightYDataRev = None)
            

    @pyqtSignature("bool")
    def on_action_PlotS_triggered(self,  checked):
        """
        Plot [S] parameters computed from current coupling matrix.
        S21 and S11 are plotted on top of [S] parameters computed from Characteristic Polynomials.
        """
        SP = self.mainWindow.SP
        
        # Compute [S] parameters from current coupling  matrix
        myPrint('Displayed coupling matrix:')
        stErrorCM, CM_error = SP.fromCouplingMatrix(self.CM.MatQ) # Do not update CM.CM_error  with these results, since the matrix may have been edited by the user.
        
        # Check if an [S] parameter plot exists and, if not, create a new one with [S] computed from CPs
        if self.mainWindow.SPlotError is None or self.mainWindow.SPlotError.closed is True: 
            # Set dynamic range to the lowest value of S21 at sweep range ends.
            dynamicRange = -20*log10( min([ abs(SP.S21[0]), abs(SP.S21[-1] ),   abs(SP.S21M[0]), abs(SP.S21M[-1] )])) 
            dynamicRange = 10*ceil(dynamicRange/10)
        
            self.mainWindow.SPlotError = DbPlot(parent=self.mainWindow,  
                                                                    windowTitle = '[S] parameters CM vs CP', 
                                                                    tabTitle = 'S11', 
                                                                    title = 'S11 parameter computed from CM and CP',
                                                                    XData = SP.freq / 1e9, 
                                                                    leftYData = [20*np.log10(np.abs(SP.S11)), 20*np.log10(np.abs(SP.S11M)), 20*np.log10(np.abs(SP.S22)), 20*np.log10(np.abs(SP.S22M)) ], 
                                                                    leftWidth = [2, 1,  2, 1], 
                                                                    leftYmax = 0,  leftYmin = -dynamicRange, 
                                                                    leftYNames = ['S<sub>11</sub> from CP',  'S<sub>11</sub> from CM', 'S<sub>22</sub> from CP',  'S<sub>22</sub> from CM'] , 
                                                                    leftYVisible = [True, True, False, False], 
                                                                    XLabel = 'Frequency (GHz)', 
                                                                    leftYLabel = 'dB',             
                                                                    Xunits = 'GHz',  leftYunits = 'dB',
                                                                    rightYData = [ SP.phaseS11 * 180/np.pi,  SP.phaseS11M * 180/np.pi, SP.phaseS22 * 180/np.pi,  SP.phaseS22M * 180/np.pi ],
                                                                    rightWidth = [2, 1, 2, 1], 
                                                                    rightYunits = 'deg', rightYLabel = 'degrees' , 
                                                                    rightYNames = ['Phase(S<sub>11</sub>) from CP',  'Phase(S<sub>11</sub>) from CM', 'Phase(S<sub>22</sub>) from CP',  'Phase(S<sub>22</sub>) from CM'], 
                                                                    rightYVisible = [False,  False,  False,  False], 
                                                                    textLabel =stErrorCM
                                                                    )
                                                                    
            self.mainWindow.SPlotError.addTab(tabTitle = 'S21', 
                                                                    title = 'S21 parameter computed from CM and CP',
                                                                    XData = SP.freq / 1e9, 
                                                                    leftYData = [20*np.log10(np.abs(SP.S21)),  20*np.log10(np.abs(SP.S21M)) ],
                                                                    leftWidth = [2, 1] , 
                                                                    leftYmax = 0,  leftYmin = -dynamicRange, 
                                                                    leftYNames = ['S<sub>21</sub> from CP',  'S<sub>21</sub> from CM'] , 
                                                                    XLabel = 'Frequency (GHz)', 
                                                                    leftYLabel = 'dB', 
                                                                    Xunits = 'GHz',  leftYunits = 'dB',
                                                                    rightYData = [ SP.phaseS21 * 180/np.pi,  SP.phaseS21M * 180/np.pi ],
                                                                    rightWidth = [2, 1], 
                                                                    rightYunits = 'deg', rightYLabel = 'degrees' , 
                                                                    rightYNames = ['Phase(S<sub>21</sub>) from CP',  'Phase(S<sub>21</sub>) from CM'], 
                                                                    rightYVisible = [False,  False], 
                                                                    )
                                                                    
            self.mainWindow.SPlotError.addTab(tabTitle = 'Group delay', 
                                                                    title = 'Group delay computed from CM and CP',
                                                                    XData = SP.freq2 / 1e9, 
                                                                    leftYData = [ 1e9 * SP.groupDelay2,  1e9 * SP.groupDelayM ], 
                                                                    leftWidth = [2, 1] , 
                                                                    leftYNames = ['Group delay from CP',  'Group delay from CM'],
                                                                    XLabel = 'Frequency (GHz)', 
                                                                    leftYLabel = 'Group delay (ns.)', 
                                                                    Xunits = 'GHz',  leftYunits = 'ns',
                                                                    )
                                                                    
        else:
            self.mainWindow.SPlotError.update(0,  XData = SP.freq / 1e9, leftYData = [20*np.log10(np.abs(SP.S11)), 20*np.log10(np.abs(SP.S11M)), 20*np.log10(np.abs(SP.S22)), 20*np.log10(np.abs(SP.S22M))],  
                                                                        rightYData = [ SP.phaseS11 * 180/np.pi,  SP.phaseS11M * 180/np.pi, SP.phaseS22 * 180/np.pi,  SP.phaseS22M * 180/np.pi ], 
                                                                        textLabel =stErrorCM, autoScaleBottomX = self.mainWindow.autoScaleFreq )
                                                                        
            self.mainWindow.SPlotError.update(1,  XData = SP.freq / 1e9, leftYData = [20*np.log10(np.abs(SP.S21)), 20*np.log10(np.abs(SP.S21M))], 
                                                                        rightYData = [ SP.phaseS21 * 180/np.pi,  SP.phaseS21M * 180/np.pi ], autoScaleBottomX = self.mainWindow.autoScaleFreq)
                                                                        
            self.mainWindow.SPlotError.update(2,  XData = SP.freq2 / 1e9, leftYData = [1e9 * SP.groupDelay2,  1e9 * SP.groupDelayM], autoScaleBottomX = self.mainWindow.autoScaleFreq  )
            
            self.mainWindow.autoScaleFreq = False
            
        
        # Plot masks, or update if there are any
        if self.mainWindow.SM is not None: 
            self.mainWindow.SPlotError.deleteMasks()
            self.mainWindow.plotMasks()            
        
    
    @pyqtSignature("int")    
    def on_CM_comboBox_currentIndexChanged(self,  index):
        """
        Load a new coupling matrix into the QTableWidget.
        This function is automatically executed by the GUI when the user changes the coupling matrix type comboBox.
        """
        if self.noLoadCM: return
        
        # Check if matrix has been modified and not saved
        if not self.okToContinue():    # self.recordChanges condition is necessary to avoid recursion
            QMessageBox.warning(self, "WARNING",  'User cancelled action.<p>Coupling Matrix has not been loaded from list.' )
            # Restore last index to comboBox
            self.noLoadCM = True
            self.CM_comboBox.setCurrentIndex(self.CM.indexCM) # Will can on_CM_comboBox_currentIndexChanged() recursively
            self.noLoadCM = False
            return
        
        MatQ = self.CM.listM[index].copy()    # Deep copy: we do not want to modify the precomputed matrices
        self.CM.MatQ = MatQ
        self.loadMatrixQ(MatQ) 
        if self.recordChanges:
            self.CM.indexCM = index
        
        self.dirty = False
        self.setWindowModified(self.dirty)
        
        # After the matrix editor has been opened for the first time, loading a matrix from the list is an edit action than modifies CM.MatQ, so it must be recorded in the changes lists.
        if self.matrixEditDlg is not None and self.recordChanges:
            self.matrixEditDlg.lastEditAction = "Matrix %s loaded from list" % MatQ.name
            self.pushEditAction()
            
        
    def pushEditAction(self):
        self.matrixEditDlg.edit_listWidget.addItem(self.matrixEditDlg.lastEditAction)
        self.matrixEditDlg.bkpActions.append(self.matrixEditDlg.lastEditAction)
        self.matrixEditDlg.edit_listWidget.setCurrentRow(self.matrixEditDlg.edit_listWidget.count()-1)    # Set edit_listWidget current row to last one
        myPrint("Done: Matrix edit action '%s'" % self.matrixEditDlg.lastEditAction)
        self.setWindowModified(self.dirty)
        self.CM_name.deselect()
        
        # Push modified CM.MatQ to backup list. Must use deep copy, since CM.MatQ will be modified by edit actions.
        self.matrixEditDlg.bkpMatQ.append(self.CM.MatQ.copy())
            
    ##@}
        
# End class: CouplingMatrixDlg #
#¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡#

#¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡#
# Begin class: ParamEditDlg #

class ParamEditDlg(QMainWindow, Ui_parameditdlg.Ui_ParamEditDlg):
    """
    GUI dialog to edit synthesis parameters
    """

    def __init__(self, parent=None):
        """
        Constructor: Creates dialog window containing values of parameters.
            Modifies MainWindow.P , using a shallow copy for easier access.
            Saves a backup copy of the mainWindow Parameters and synthesis results, that will be restored if the user Discards the dialog.
            @param parent = Parent widget, in this case is mainWindow.
        """
        super(ParamEditDlg, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        ##
        # Index of the Center Frequency  Units comboBox.
        # It is used to check if the user has changed the Frequency Units in the GUI and to update the Frequency value accordingly.
        self.f0UnitsIndex = None
     
        ##
        # Index of the Bandwidth Units comboBox.
        # It is used to check if the user has changed the Bandwidth Units in the GUI and to update the Bandwidth value accordingly.
        self.BWUnitsIndex = None
        
        ##
        # Index of the Quasieliptic zero frequency  units comboBox.
        # It is used to check if the user has changed the Frequency Units in the GUI and to update the Frequency value accordingly.
        self.qeZeroUnitsIndex = None
        
        ##
        # Index of the Generalized Chebyshev zeros frequency  units comboBox.
        # It is used to check if the user has changed the Frequency Units in the GUI and to update the Frequency value accordingly.
        self.transmissionZerosUnitsIndex = None

        ##
        # Index of the Minimum Frequency  Units comboBox.
        # It is used to check if the user has changed the Frequency Units in the GUI and to update the Frequency value accordingly.
        self.minFreqUnitsIndex = None
        
        ##
        # Index of the Maximum Frequency  Units comboBox.
        # It is used to check if the user has changed the Frequency Units in the GUI and to update the Frequency value accordingly.
        self.maxFreqUnitsIndex = None

        ##
        # Variable that becomes true when the user clicks the 'Discard' button to reject the dialog.
        # Its is necessary to use this variable, since now the dialog is a QMainWindow instead of a QDialog, and QMainWindow does not have a reject() slot.
        self.rejected = False
        
        ##
        # Variable that is False when it is necessary to recompute synthesis because Parameters have been modified.
        self.recomputed = True
        
        ##
        # Variable to store a list of widgets with bad input.
        # Only when this list is empty, he "Accept" and "Compute" buttons are enabled.
        self.badInput = [ ]
        
        ##
        # Variable to store sweep value in order to check if it has been changed by the user, and then update frequency axis. Tuple (fmin, fmax)
        oldSweep = None
        
        ##
        # Copy of the mainWindow class instance.
        # It is necessary  to store a copy since it must be accessed by some member functions.
        self.mainWindow = parent
        self.setupUi(self)
        
#        if importedExtraFilters: 
#            self.topol_comboBox.removeItem(self.topol_comboBox.findText(u'Quasieliptic'))
#            self.filter_stackedWidget.removeWidget(self.filterQuasieliptic_page)
        
        ##
        # Shallow copy just for easier access.
        self.P = self.mainWindow.P

        ##
        # Backup copy of the mainWindow Parameters and synthesis results, that will be restored if the user Discards the dialog.
        self.resultBkp = copy.deepcopy( (self.mainWindow.P,  self.mainWindow.CP,  self.mainWindow.CM,  self.mainWindow.SP,  self.mainWindow.FT) )
        
        ##
        # Initial dialog size as set in Qt designer
        self.dlgInitSize = self.size().height()
        
        # Set active tab to 'Filter parameters'
        self.tabWidget.setCurrentIndex(0)
        
        # Connect self.N_spinBox to a method that updates pre-distortion zeros arrangement.
        self.connect(self.N_spinBox,  SIGNAL("valueChanged(int)"), self.updatePredisZerosArrang )
        
        # Create adaptive predistortion weights table
        self.wFunc_horizontalLayout = QHBoxLayout()
        self.wFunc_horizontalLayout.addStretch()
        self.wFunc_tableWidget = myTableWidget(name = 'Adaptive predistortion weights', parent = self.adapPredis_groupBox)
        self.wFunc_tooltip = "Adaptive predistortion weights.\nThe shift is equal to the weight times sigma:\nweights = 1 lead to conventional pole shifting.\nTable entries are editable."
        self.wFunc_tableWidget.setToolTip(QApplication.translate("ParamEditDlg", self.wFunc_tooltip, None, QApplication.UnicodeUTF8))
        self.wFunc_tableWidget.horizontalHeader().setVisible(False)
        self.wFunc_horizontalLayout.addWidget(self.wFunc_tableWidget)
        self.wFunc_horizontalLayout.addStretch()
        self.wFunc_verticalLayout.addLayout(self.wFunc_horizontalLayout)
        self.wFunc_verticalLayout.setAlignment(Qt.AlignHCenter)
        self.connect(self.N_spinBox,  SIGNAL("valueChanged(int)"), lambda x: self.updatewFuncTableSize(self.P.wFunc,  (self.N_spinBox.value(),  1),  True ) )
        self.connect(self.adapPredis_groupBox,  SIGNAL("toggled(bool)"), lambda x: self.updatewFuncTableSize(self.P.wFunc,  (self.N_spinBox.value(),  1),  True ) )
        self.connect(self.wFunc_tableWidget, SIGNAL("itemChanged(QTableWidgetItem*)"), lambda x: self.updatewFuncTableSize(self.P.wFunc,  (self.N_spinBox.value(),  1),  False ))
        
        # Create GenCheby transmission zeros List
        self.genChebyTZs_horizontalLayout = QHBoxLayout()
        self.genChebyTZs_horizontalLayout.addStretch()
        self.genChebyTZs_tableWidget = myTableWidget(name = 'Transmission zeros', mainDlg = self, parent = self.genChebyLP_groupBox)
        self.genChebyTZs_tableWidget.setUnits('MHz')
        self.genChebyTZs_tooltip = "Transmission zeros (complex or purely imaginary). \nReal part in normalized frequency units and imaginary part in MHz. Imaginary part cannot be 0 MHz. \nFor complex zeros, a symmetric zero with opposite real part is automatically added."
        self.genChebyTZs_tableWidget.setToolTip(QApplication.translate("ParamEditDlg", self.genChebyTZs_tooltip, None, QApplication.UnicodeUTF8))
        self.genChebyTZs_tableWidget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.genChebyTZs_horizontalLayout.addWidget(self.genChebyTZs_tableWidget)
        self.genChebyTZs_horizontalLayout.addStretch()
        self.genCheby_verticalLayout.insertLayout(2,  self.genChebyTZs_horizontalLayout)
        self.genCheby_verticalLayout.setAlignment(Qt.AlignHCenter)
        
        self.connect(self.genChebyTZs_tableWidget, SIGNAL("cellChanged(int, int)"), self.genChebyTZs_tableWidget.tableCellChanged)
        
        # Create GenCheby LP complex zeros List
        self.LPcomplexZeros_horizontalLayout = QHBoxLayout()
        self.LPcomplexZeros_horizontalLayout.addStretch()
        self.LPcomplexZeros_tableWidget = myTableWidget(name = 'Initial positions of complex zeros to optimize', mainDlg = self, parent = self.genChebyLP_groupBox)
        self.LPcomplexZeros_tableWidget.setUnits('MHz')
        self.LPcomplexZeros_tooltip = "Initial positions of complex zeros to optimize for linear phase equalization. \nReal part in normalized frequency units and imaginary part in MHz. \nImaginary part of a complex zero cannot be 0 MHz. \nIf imaginary part is empty, the zero is purely real.\nA symmetric zero with opposite real part is automatically added."
        self.LPcomplexZeros_tableWidget.setToolTip(QApplication.translate("ParamEditDlg", self.LPcomplexZeros_tooltip, None, QApplication.UnicodeUTF8))
        self.LPcomplexZeros_tableWidget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.LPcomplexZeros_horizontalLayout.addWidget(self.LPcomplexZeros_tableWidget)
        self.LPcomplexZeros_horizontalLayout.addStretch()
        self.genChebyLPverticalLayout.insertLayout(4,  self.LPcomplexZeros_horizontalLayout)
        self.genChebyLPverticalLayout.setAlignment(Qt.AlignHCenter)
        
        self.connect(self.LPcomplexZeros_tableWidget, SIGNAL("cellChanged(int, int)"), self.LPcomplexZeros_tableWidget.tableCellChanged)
        
        # Update TZs tables when changing f0
        self.connect(self.f0_lineEdit, SIGNAL("editingFinished()"), self.genChebyTZs_tableWidget.setf0 )
        self.connect(self.f0_lineEdit, SIGNAL("editingFinished()"), self.LPcomplexZeros_tableWidget.setf0 )
        
        # Load parameters into GUI widgets
        self.updateDialogParams()
    
        # Create validators. Validator regular expressions are global variables.
        #realValidator = QDoubleValidator(self)
        realValidator = QRegExpValidator( reReal ,  self)
        #realPositiveValidator = QDoubleValidator(self)
        #realPositiveValidator.setBottom(0)
        realPositiveValidator = QRegExpValidator( reRealPositive,  self)
        realPositiveValidatorInf = QRegExpValidator( reRealPositiveInf,  self)
        complexValidator = QRegExpValidator( reComplex,  self)
        realListValidator              = QRegExpValidator( reRealList,  self)
        realPositiveListValidator  = QRegExpValidator( reRealPositiveList,  self)
        complexListValidator        = QRegExpValidator( reComplexList,  self)
        
        # Set validators
        self.f0_lineEdit.setValidator(realPositiveValidator)
        self.BW_lineEdit.setValidator(realPositiveValidator)
        self.maxFreq_lineEdit.setValidator(realPositiveValidator)
        self.minFreq_lineEdit.setValidator(realPositiveValidator)
        self.chebyLr_lineEdit.setValidator(realPositiveValidator)
        self.qeLr_lineEdit.setValidator(realPositiveValidator)
        self.qeZero_lineEdit.setValidator(realPositiveValidator)
        self.genChebyLr_lineEdit.setValidator(realPositiveValidator)
#        self.transmissionZeros_lineEdit.setValidator(realPositiveListValidator)
#        self.LPcomplexZeros_lineEdit.setValidator(complexListValidator)
#        self.LPfracBW_lineEdit.setValidator(realPositiveValidator)     # Now this widget is a doubleSpinBox
#        self.LPripple_lineEdit.setValidator(realPositiveValidator)       # Now this widget is a doubleSpinBox
        self.Qo_lineEdit.setValidator(realPositiveValidatorInf)
        self.QoPredis_lineEdit.setValidator(realPositiveValidatorInf)
        self.Qef_lineEdit.setValidator(realPositiveValidatorInf)
#        self.wFunc_lineEdit.setValidator(realListValidator)

        self.nuqK11c1_lineEdit.setValidator(realPositiveValidator)
        self.nuqK22c1_lineEdit.setValidator(realPositiveValidator)
        self.nuqK21c1_lineEdit.setValidator(realPositiveValidator)
        self.nuqK21c2_lineEdit.setValidator(realPositiveValidator)
        self.nuqK21c3_lineEdit.setValidator(realPositiveValidator)
        
        self.nuqDelta_lineEdit.setValidator(realPositiveValidator)
        
        ##
        # Variable to store the modified/unmodifed status of the dialog working copy of parameters self.P.
        # The dialogChanged() method is automatically called when the user modifies the edit widgets contents and sets this variable to True.
        # This variable must be set to False at the end of the __init__ dialog constructor, 
        # since dialogChanged() is called when the constructor sets the edit widgets contents.
        self.dialogModified = False
        
        
    def updateDialogParams(self,  autoFreqUnits = True):
        """
        Updates paramEditDlg widgets with contents of local copy of P (self.P).
            @param autoFreqUnits =  If True, automatically update frequency units in ComboBoxes. It is True by default.
        """

        # Local copy of parameters
        P = self.P
        
        # Set output file and directory names
        if self.P.outName is not None:  self.outName_lineEdit.setText(self.P.outName)
        if self.P.outDir is not None:  self.outDir_lineEdit.setText(self.P.outDir)
    
        # Set Synthesis parameters widgets
        if self.P.N is not None:  self.N_spinBox.setValue(self.P.N)
        
        if self.P.f0 is not None:  
            (freq,  units) = self.frequencyUnits(self.P.f0)
            autoUnitsIndex = self.f0_comboBox.findText(units)
            
            if autoFreqUnits:   # Set automatic frequency units
                self.f0_lineEdit.setText(str(freq))
                self.f0_comboBox.setCurrentIndex(autoUnitsIndex)
                self.f0UnitsIndex = autoUnitsIndex
            else: # Keep user frequency units
                userUnitsIndex = self.f0_comboBox.currentIndex()
                newValue = freq * pow(10,  3*(autoUnitsIndex-userUnitsIndex))
                self.f0_lineEdit.setText(str(newValue))
                self.f0UnitsIndex = userUnitsIndex            
        else:             
            self.f0UnitsIndex = self.f0_comboBox.currentIndex()
            
        # Update f0 in TZs tables, for computation of f-f0 in 3rd column
        self.genChebyTZs_tableWidget.setf0(False)
        self.LPcomplexZeros_tableWidget.setf0(False)
        
        if self.P.BW is not None:
            (freq,  units) = self.frequencyUnits(self.P.BW)
            autoUnitsIndex = self.BW_comboBox.findText(units)
            
            if autoFreqUnits:   # Set automatic frequency units
                self.BW_lineEdit.setText(str(freq))
                self.BW_comboBox.setCurrentIndex(autoUnitsIndex)
                self.BWUnitsIndex = autoUnitsIndex
            else: # Keep user frequency units
                userUnitsIndex = self.BW_comboBox.currentIndex()
                newValue = freq * pow(10,  3*(autoUnitsIndex-userUnitsIndex))
                self.BW_lineEdit.setText(str(newValue))
                self.BWUnitsIndex = userUnitsIndex                   
        else:
            self.BWUnitsIndex = self.BW_comboBox.currentIndex()

        if self.P.filterTransferFunc is not None:
            topol = self.P.filterTransferFunc.title()   # Capital initials for safe comparison with GUI ComboBox items
            iTopol = self.topol_comboBox.findText(topol)
        else: iTopol = 0
            
        if iTopol < 0:
            QMessageBox.warning(self,  "WARNING",  "Unknown Filter Topology '%s' replaced by default value." % self.P.filterTransferFunc)
            iTopol = 0
        self.topol_comboBox.setCurrentIndex(iTopol)
        self.filter_stackedWidget.setCurrentIndex(iTopol)

        # Set filter topology widgets
        if self.P.chebyLr is not None: self.chebyLr_lineEdit.setText(str(self.P.chebyLr))
        if self.P.qeLr is not None:      self.qeLr_lineEdit.setText(str(self.P.qeLr))
        
        #if self.P.qeZero is not None: self.qeZero_lineEdit.setText(str(self.P.qeZero))
        if self.P.qeZero is not None:  
            (freq,  units) = self.frequencyUnits(self.P.qeZero)
            autoUnitsIndex = self.qeZero_comboBox.findText(units)
            
            if autoFreqUnits:   # Set automatic frequency units
                self.qeZero_lineEdit.setText(str(freq))
                self.qeZero_comboBox.setCurrentIndex(autoUnitsIndex)
                self.qeZeroUnitsIndex = autoUnitsIndex
            else: # Keep user frequency units
                userUnitsIndex = self.qeZero_comboBox.currentIndex()
                newValue = freq * pow(10,  3*(autoUnitsIndex-userUnitsIndex))
                self.qeZero_lineEdit.setText(str(newValue))
                self.qeZeroUnitsIndex = userUnitsIndex                            
        else:             
            self.qeZeroUnitsIndex = self.qeZero_comboBox.currentIndex()
        
        if self.P.genChebyLr is not None:       self.genChebyLr_lineEdit.setText(str(P.genChebyLr))
        
        #if self.P.transImagZeros is not None: self.transmissionZeros_lineEdit.setText(filter(lambda c: c not in "()[]", str(P.transImagZeros)))
        
        # P.validation() ensures that when there are no TZs or LPZs, lists are not None, but empty.
        
        listImaginaryPartsZeros = self.P.transImagZeros + np.imag( self.P.transComplexZeros + self.P.LPcomplexZeros ).tolist()
        if len(listImaginaryPartsZeros) > 0:  
            (freq,  units) = self.frequencyUnits(listImaginaryPartsZeros)
            autoUnitsIndex = self.transmissionZeros_comboBox.findText(units)
            
            if autoFreqUnits:   # Set automatic frequency units
#                self.transmissionZeros_lineEdit.setText(filter(lambda c: c not in "()[]", str(freq)))
                self.transmissionZeros_comboBox.setCurrentIndex(autoUnitsIndex)
                self.transmissionZerosUnitsIndex = autoUnitsIndex
            else: # Keep user frequency units
                userUnitsIndex = self.transmissionZeros_comboBox.currentIndex()
                self.transmissionZerosUnitsIndex = userUnitsIndex                      
        else:             
            self.transmissionZerosUnitsIndex = self.transmissionZeros_comboBox.currentIndex()
            
        if self.P.genChebyLP is not None:          self.genChebyLP_groupBox.setChecked(bool(self.P.genChebyLP))
#        if self.P.LPcomplexZeros is not None:   self.LPcomplexZeros_lineEdit.setText(filter(lambda c: c not in "()[]", str(P.LPcomplexZeros)))

        if self.P.LPalgor is not None:
            algor = self.P.LPalgor.upper()  # Uppercase letters for safe comparison with GUI ComboBox items
            iAlgor = self.LPalgor_comboBox.findText(algor)
        else:
            iAlgor = 4
            
        if iAlgor < 0:
            QMessageBox.warning(self,  "WARNING",  "Unknown Linear Phase Optimization Algorithm '%s' replaced by default value." % self.P.LPalgor)
            iAlgor = 4
        self.LPalgor_comboBox.setCurrentIndex(iAlgor)

        if self.P.LPmaxIter is not None:               self.LPmaxIter_spinBox.setValue(self.P.LPmaxIter)
        if self.P.LPNsamples is not None:           self.LPNsamples_spinBox.setValue(self.P.LPNsamples)
        if self.P.LPfracBW is not None:               self.LPfracBW_doubleSpinBox.setValue(self.P.LPfracBW)
        if self.P.LPripple is not None:                 self.LPripple_doubleSpinBox.setValue(self.P.LPripple * 1e9)
        
        # Update table Widgets with TZs. self.updateTZsTable() resizes, fills and adjusts the table.
        TZsList = np.concatenate( (1j*np.array(self.P.transImagZeros),  np.array(self.P.transComplexZeros)) ).tolist()
        self.updateTZsTable(self.genChebyTZs_tableWidget, TZsList)
        self.updateTZsTable(self.LPcomplexZeros_tableWidget,  self.P.LPcomplexZeros + self.P.LPrealZeros)
        
        # Update total number of zeros in transmissionZeros_lcdNumber
        self.updateTotaltransmissionZeros()
        
        # Set Synthesis parameters widgets
        if self.P.synthTech is not None:
            tech = self.P.synthTech.title()  # Capital initials for safe comparison with GUI ComboBox items
            iTech = self.synthTech_comboBox.findText(tech)
        else:
            iTech = 0
            
        if iTech < 0:
            QMessageBox.warning(self,  "WARNING",  "Unknown Synthesis Technique '%s' replaced by default value." % self.P.synthTech)
            iTech = 0
        self.synthTech_comboBox.setCurrentIndex(iTech)
        self.synthTech_stackedWidget.setCurrentIndex(iTech)
        
        # Lossless parameters
        if self.P.finiteQo is not None:        self.finiteQo_groupBox.setChecked(bool(self.P.finiteQo))
        if self.P.Qo is not None:                self.Qo_lineEdit.setText(self.float2stInf(self.P.Qo))
        
        # Predistortion parameters
        if self.P.zerosArrang is not None:  
            if type(self.P.zerosArrang).__name__ == 'list':  self.zerosArrang_comboBox.setCurrentIndex(0)
            else:                                                                     self.zerosArrang_comboBox.setCurrentIndex(self.P.zerosArrang)
        if self.P.adapPredis is not None:   self.adapPredis_groupBox.setChecked(bool(self.P.adapPredis))
        if self.P.QoPredis is not None:       self.QoPredis_lineEdit.setText(self.float2stInf(self.P.QoPredis))
        if self.P.Qef is not None:                self.Qef_lineEdit.setText(self.float2stInf(self.P.Qef))
        
        # Update lineEdit with weights list
        #if self.P.wFunc is not None:           self.wFunc_lineEdit.setText(filter(lambda c: c not in "()[]", str(P.wFunc)))
        
        # Update table Widget with weights list. self.updatewFuncTableSize() resizes, fills and adjusts the table.
        self.updatewFuncTableSize(self.P.wFunc,  (self.P.N, 1),  True)
        
        # NU-Q Parameters
        if self.P.nuqK11c1 is not None:           self.nuqK11c1_lineEdit.setText(str(self.P.nuqK11c1))
        if self.P.nuqK22c1 is not None:           self.nuqK22c1_lineEdit.setText(str(self.P.nuqK22c1))
        if self.P.nuqK21c1 is not None:           self.nuqK21c1_lineEdit.setText(str(self.P.nuqK21c1))
        if self.P.nuqK21c2 is not None:           self.nuqK21c2_lineEdit.setText(str(self.P.nuqK21c2))
        if self.P.nuqK21c3 is not None:           self.nuqK21c3_lineEdit.setText(str(self.P.nuqK21c3))
        
        if self.P.nuqTech is not None:
            nuqTech= self.P.nuqTech   # No capital initials in this case
            iNuqTech = self.nuqTech_comboBox.findText(nuqTech)
        else:
            iNuqTech = 0
            
        if iNuqTech < 0:
            QMessageBox.warning(self,  "WARNING",  "Unknown Prescribed Insertion Loss technique '%s' replaced by default value." % self.P.nuqTech)
            iNuqTech = 0
        self.nuqTech_comboBox.setCurrentIndex(iNuqTech)
        self.nuqTech_stackedWidget.setCurrentIndex(iNuqTech)
        
        if self.P.nuqDelta is not None: self.nuqDelta_lineEdit.setText(str(self.P.nuqDelta))        

        # Set sweep parameters widgets
        if self.P.minFreq is not None:
            (freq,  units) = self.frequencyUnits(self.P.minFreq)
            autoUnitsIndex = self.minFreq_comboBox.findText(units)
            
            if autoFreqUnits:   # Set automatic frequency units
                self.minFreq_lineEdit.setText(str(freq))
                self.minFreq_comboBox.setCurrentIndex(autoUnitsIndex)
                self.minFreqUnitsIndex = autoUnitsIndex
            else: # Keep user frequency units
                userUnitsIndex = self.minFreq_comboBox.currentIndex()
                newValue = freq * pow(10,  3*(autoUnitsIndex-userUnitsIndex))
                self.minFreq_lineEdit.setText(str(newValue))
                self.minFreqUnitsIndex = userUnitsIndex
        else:
            self.minFreqUnitsIndex = self.minFreq_comboBox.currentIndex()

        if self.P.maxFreq is not None:
            (freq,  units) = self.frequencyUnits(self.P.maxFreq)
            autoUnitsIndex = self.maxFreq_comboBox.findText(units)
            
            if autoFreqUnits:   # Set automatic frequency units
                self.maxFreq_lineEdit.setText(str(freq))
                self.maxFreq_comboBox.setCurrentIndex(autoUnitsIndex)
                self.maxFreqUnitsIndex = autoUnitsIndex
            else: # Keep user frequency units
                userUnitsIndex = self.maxFreq_comboBox.currentIndex()
                newValue = freq * pow(10,  3*(autoUnitsIndex-userUnitsIndex))
                self.maxFreq_lineEdit.setText(str(newValue))
                self.maxFreqUnitsIndex = userUnitsIndex
        else:
            self.maxFreqUnitsIndex = self.maxFreq_comboBox.currentIndex()
        
        if self.P.numFreq is not None: self.numFreq_spinBox.setValue(self.P.numFreq)        
        
        self.oldSweep = (P.minFreq, P.maxFreq)
        

    def updatewFuncTableEntries(self,  list):
        """ 
        Update predistorsion weights table Widget with list.
            @param list  = list containing entries to set in tableWidget
        """
        for n in range(len(list)):
            #item = QTableWidgetItem( '%.*g' % (saveSignificantDigits, list[n]) ) 
            item = QTableWidgetItem( complexStr(list[n],  saveSignificantDigits,  eng = True) ) 
            
            item.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            self.wFunc_tableWidget.setItem(0, n, item)
            
            
    def readwFuncTableEntries(self):
        """ 
        Read list from predistorsion weights table Widget entries.
        If row or column count is equal to 1, the 2D list is collapsed to a 1D list.
            @return list  = list containing entries in tableWidget
        """
        table = self.wFunc_tableWidget
        M = table.rowCount()
        list = []
        for m in range(M):
            item = table.item(m, 0) 
            try:
                if item is None: raise ValueError,  'Empty cell'
                value = float(item.text())
            except ValueError,  err:
                QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect table entry:<br>%s</p>' % err)
                if item is not None: item.setText('')
            else: list.append(value)
        if len(list) == 1: list = list[0]
        
        return list
        
        
    def readTZsTableEntries(self,  table):
        """ 
        Read list from TZs table Widget entries.
            @param table = tableWidget to read
            @return listImag = list containing purely imaginary zeros entries in tableWidget
            @return listComplex = list containing complex zeros entries in tableWidget
            @return listReal =  list containing purely real zeros entries in tableWidget
        """

        M = table.rowCount()
        listImag = []
        listComplex = []
        listReal = []

        for m in range(M):
            reItem = table.item(m, 0) 
            stRe = str(reItem.text()).strip() if reItem is not None else ''
            try:
                re = float(stRe)  if stRe != '' else 0
            except ValueError,  err:
                QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect table entry:<br>%s</p>' % err)
                table.currentItem().setText('')
                stRe = ''
                re = 0
                
            if re == 0 and stRe != '': table.currentItem().setText('') # Replace zeros by empty strings in table cells
            
            imItem = table.item(m, 1) 
            stIm = str(imItem.text()).strip() if imItem is not None else ''
            
            if stIm != '':  # Complex zero or purely imaginary
                try:
                    im = float(stIm)
                except ValueError,  err:
                    QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect table entry:<br>%s</p>' % err)
                    table.currentItem().setText('')
                    continue

                # DO NOT replace imaginary part by '': the former is a complex zero with 0 Hz imag part while the later is a real zero.
                # if im ==0: table.currentItem().setText('') # Replace zeros by empty strings in table cells
            
                if re==0 and im==0: continue      # Empty table row
            
                stError = '<p align="center">Incorrect table entry at row %d:<br>%s</p><p align="center"><b>Please fix it.</b></p>'
                if im < 0: 
                   QMessageBox.critical(self,  "ERROR", stError % (m+1,  'Imaginary part of a transmission zero cannot be negative Hz.'))             
                if im == 0: 
                    QMessageBox.critical(self,  "ERROR", stError % (m+1,  'Imaginary part of a transmission zero cannot be 0 Hz.'))  
                    
                # append zero to list
                if re == 0: listImag.append(im * pow(10,  3*self.transmissionZerosUnitsIndex))
                else:         listComplex.append(re + 1j*im * pow(10,  3*self.transmissionZerosUnitsIndex))      
        
            else: # Real zero
                if re==0: continue      # Empty table row
                listReal.append(re)
            
        return listImag,  listComplex,  listReal
        
        
    def updateTZsTable(self,  table,  list):
        """
        Updates a TZs tableWidget with contents of list, and
        makes the number of rows equal to the number of zeros.
        Adjusts widget size to table contents.
        This function is called by the self.updateDialogParams() function.
            @param table = tableWidget to update
            @param list  = list containing entries to set in tableWidget
        """
        table.setRowCount(len(list))
        table.setColumnCount(3)
        stUnits = self.transmissionZeros_comboBox.itemText(self.transmissionZerosUnitsIndex)
        table.setHorizontalHeaderLabels( ['Real',  'Imag (%s)' % stUnits,  'Imag-f0 (%s)' % stUnits ] )
        table.setToolTip(unicode(table.toolTip()).replace(table.units, stUnits))
        table.setUnits(stUnits)

        # Table is not being updated by the user!
        table.updatedByUser = False
        
        for n in range(len(list)):
            if list[n].real != 0: reItem = QTableWidgetItem( '%.*g' % (saveSignificantDigits, list[n].real) )
            else: reItem = QTableWidgetItem('')
            reItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            table.setItem(n, 0, reItem)
            
            if list[n].imag != 0: 
                imItem = QTableWidgetItem(  '%.*g' % (saveSignificantDigits, list[n].imag / pow(10,  3*self.transmissionZerosUnitsIndex)) )
                imItemf0 = QTableWidgetItem(  '%.*g' % (saveSignificantDigits, (list[n].imag - table.f0) / pow(10,  3*self.transmissionZerosUnitsIndex)) )
            else: 
                imItem = QTableWidgetItem('')
                imItemf0 = QTableWidgetItem('')
            imItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)                            
            imItemf0.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)                            
            table.setItem(n, 1, imItem)
            table.setItem(n, 2, imItemf0)
        
        table.updatedByUser = True

        table.updateTableSize()
    
    
    def frequencyUnits(self,  freq): 
        """
        Returns frequency expressed in GHz, MHz, 'kHz' or Hz.
            @param freq = Frequency in Hz. Can be a float or a list of floats.
            @return (newFreq, units).
            @n newFreq = Frequency expressed in units.
            @n units = 'GHz', 'MHz', 'kHz' or 'Hz'.
        """
        if freq == 0: return (0, 'Hz')
        
        if type(freq) in [ types.FloatType, types.IntType, types.ComplexType  ]: freq = [ freq ]
        
        afreq = np.array(freq)
        freqExp = log10(np.prod(afreq)) / len(freq)
        
        if freqExp >= 9: 
            newFreq = afreq / 1e9
            units = 'GHz'
        elif freqExp >= 6: 
            newFreq = afreq / 1e6
            units = 'MHz'
        elif freqExp >= 3: 
            newFreq = afreq / 1e3
            units = 'kHz'
        else: 
            newFreq = afreq
            units =  'Hz'
        
        newFreq = newFreq.tolist()
        if len(newFreq) == 1: newFreq = newFreq[0]
        
        return newFreq,  units


    def st2floatInf(self, st):
        """
        Convert string to float. 'INF', 'Inf', 'inf' are converted to numpy.inf.
        @param st = String to convert.
        @return value = Numerical value of string (float).
    """
        return np.inf if st.lower().lstrip().rstrip()=='inf' else float(st)


    def float2stInf(self, value):
        """
        Convert float to string. numpy.inf is converted to 'Inf'.
        @param value = Float value to convert.
        @return st =  String.
    """
        return 'Inf' if value==np.inf else str(value)


    def readDialogParams(self):
        """
        Reads parameter values from dialog and store in self.P (dialog working copy of parameters).
        Error control only for invalid parameter values that would crash the parser or the GUI.
        Empty lineEdit boxes are allowed in parameters that will not be used in the computation.
        More error control in lossyfilters.Parameters.validate() .
            @return ok (True / False) = If True, parameters have been read correctly, if False, there has been an error.
        """
        
        try:
            ##
            # key is used to store parameter name for error catching
            key = 'Filter / Filter order'
            self.P.N = self.N_spinBox.value()
            
            key = 'Name of results output files'
            if self.outName_lineEdit.text() == u'': raise ValueError,  'Empty string'
            self.P.outName = unicode(self.outName_lineEdit.text())
            
            key = 'Output directory'
            if self.outDir_lineEdit.text() == u'': self.P.outDir = None
            self.P.outDir = unicode(self.outDir_lineEdit.text())
            
            key = 'Filter / Center Frequency'
            self.P.f0 = float(self.f0_lineEdit.text()) * 10**(3*self.f0UnitsIndex)
            
            key = 'Filter / Bandwidth'
            self.P.BW = float(self.BW_lineEdit.text()) * 10**(3*self.BWUnitsIndex)
            
            key = 'Filter / Filter Topology'
            self.P.filterTransferFunc = unicode(self.topol_comboBox.currentText())
            
            key = 'Filter / Chebyshev / Return losses'
            if unicode(self.chebyLr_lineEdit.text()) != u'' or self.P.filterTransferFunc == u'Chebyshev': 
                self.P.chebyLr = float(self.chebyLr_lineEdit.text())
            
            key = 'Filter / Quasieliptic / Return losses'
            if unicode(self.qeLr_lineEdit.text()) != u'' or self.P.filterTransferFunc == u'Quasieliptic': 
                self.P.qeLr = float(self.qeLr_lineEdit.text())
            
            key = 'Filter / Quasieliptic / Transmission zero'
            if unicode(self.qeZero_lineEdit.text()) != u'' or self.P.filterTransferFunc == u'Quasieliptic': 
                self.P.qeZero = float(self.qeZero_lineEdit.text())  * 10**(3*self.qeZeroUnitsIndex)
            
            key = 'Filter / Generalized Chebyshev / Return losses'
            if unicode(self.genChebyLr_lineEdit.text()) != u'' or self.P.filterTransferFunc == u'Generalized Chebyshev': 
                self.P.genChebyLr = float(self.genChebyLr_lineEdit.text())
            
            key = 'Filter / Generalized Chebyshev / Transmission Zeros'
            
            # Allow an empty transmission zero list. Generalized Chebyshev will be equivalent to Chebyshev design.
            if self.P.filterTransferFunc == u'Generalized Chebyshev': 
                
                #self.P.transImagZeros = [ n * 10**(3*self.transmissionZerosUnitsIndex) for n in map(float, str(self.transmissionZeros_lineEdit.text()).split(',')) ] if unicode(self.transmissionZeros_lineEdit.text()).lstrip() != u'' else [ ] 
                
                # Read transmission zeros and separate in list of complex and list of imaginary transmission zeros
                self.P.transImagZeros,  self.P.transComplexZeros,  transRealZeros = self.readTZsTableEntries(self.genChebyTZs_tableWidget)
                if len(transRealZeros) > 0: raise ValueError,  'Transmission zeros cannot be purely real,\nthey must have imaginary part > 0 Hz'
                
                # For list of complex
                #self.P.transImagZeros = map(complex, str(self.transmissionZeros_lineEdit.text()).replace('i', 'j').split(',')) if unicode(self.transmissionZeros_lineEdit.text()) != u'' else [ ]
                
                key = 'Filter / Generalized Chebyshev / Linear Phase'
                self.P.genChebyLP = int(self.genChebyLP_groupBox.isChecked())
                
                if self.P.genChebyLP:
#                    key = 'Filter / Generalized Chebyshev / Linear Phase / Complex transmission zeros'
#                    self.P.LPcomplexZeros = map(complex, str(self.LPcomplexZeros_lineEdit.text()).replace(' ', '').replace('i', 'j').split(',')) if unicode(self.LPcomplexZeros_lineEdit.text()) != u'' else [ ]
                    
#                    # Read a list of real numbers
#                    key = 'Filter / Generalized Chebyshev / Linear Phase / Real transmission zeros'
#                    self.P.LPrealZeros = map(float, str(self.LPrealZeros_lineEdit.text()).split(',')) if unicode(self.LPrealZeros_lineEdit.text()) != u'' else [ ]

                    key = 'Filter / Generalized Chebyshev / Linear Phase equalization / Algorithm'
                    self.P.LPalgor = unicode(self.LPalgor_comboBox.currentText())
                    
                    key = 'Filter / Generalized Chebyshev / Linear Phase equalization / Max iter'
                    self.P.LPmaxIter = self.LPmaxIter_spinBox.value()
                    
                    key = 'Filter / Generalized Chebyshev / Linear Phase equalization / N samples'
                    self.P.LPNsamples = self.LPNsamples_spinBox.value()
                    
                    key = 'Filter / Generalized Chebyshev / Linear Phase equalization / Fractional bandwidth to optimize'
                    self.P.LPfracBW = self.LPfracBW_doubleSpinBox.value()
                    
                    key = 'Filter / Generalized Chebyshev / Linear Phase equalization / Group delay ripple'
                    self.P.LPripple = self.LPripple_doubleSpinBox.value() / 1e9
                    
                    key = 'Filter / Generalized Chebyshev / Linear Phase equalization / Initial position of complex transmission zeros'
                    imagZerosList,  self.P.LPcomplexZeros,  self.P.LPrealZeros = self.readTZsTableEntries(self.LPcomplexZeros_tableWidget)
                    if len(imagZerosList) > 0: raise ValueError,  'Linear Phase optimization zeros cannot be purely imaginary'
            
            key = 'Synthesis / Synthesis Technique'
            self.P.synthTech = unicode(self.synthTech_comboBox.currentText())
            
            key = 'Synthesis / Minimum Insertion Loss / Finite Q'
            self.P.finiteQo = int(self.finiteQo_groupBox.isChecked())
            
            key = 'Synthesis / Minimum Insertion Loss / Q of implementation'
            if unicode(self.Qo_lineEdit.text()) != u'' or ( self.P.synthTech == u'Minimum Insertion Loss' and self.P.finiteQo == 1 ): 
                self.P.Qo = self.st2floatInf(unicode(self.Qo_lineEdit.text()))
            
            key = 'Synthesis / Predistortion / Arrangement of the reflection zeros'
            if self.zerosArrang_comboBox.currentIndex() == 0:   
                # If self.P.zerosArrang has been changed from 'int' to user-selected, create new list of bool, otherwise do not touch it
                if type(self.P.zerosArrang).__name__ == 'int': 
                    if self.P.zerosArrang == 1: self.P.zerosArrang = [ True for n in range(self.P.N) ]
                    if self.P.zerosArrang == 2: self.P.zerosArrang = [ False for n in range(self.P.N) ]
                    if self.P.zerosArrang == 3: self.P.zerosArrang = [ bool(n%2) for n in range(self.P.N) ]
                    if self.P.zerosArrang == 4: self.P.zerosArrang = [ not bool(n%2) for n in range(self.P.N) ]
            else:
                self.P.zerosArrang = self.zerosArrang_comboBox.currentIndex()
            
            key = 'Synthesis / Predistortion / Adaptive predistorsion'
            self.P.adapPredis = int(self.adapPredis_groupBox.isChecked())
            
            key = 'Synthesis / Predistortion / Q to implement'
            if unicode(self.QoPredis_lineEdit.text()) != u'' or self.P.synthTech == u'Predistortion': 
                self.P.QoPredis = self.st2floatInf(unicode(self.QoPredis_lineEdit.text()))
            
            key = 'Synthesis / Predistortion / Q to emulate'
            if unicode(self.Qef_lineEdit.text()) != u'' or self.P.synthTech == u'Predistortion': 
                self.P.Qef = self.st2floatInf(unicode(self.Qef_lineEdit.text()))
            
            key = 'Synthesis / Predistortion / Weight function for the pole shifting'

            if self.P.synthTech == u'Predistortion' and self.P.adapPredis == 1:             
#                # Read weights from lineEdit into list
#                if unicode(self.wFunc_lineEdit.text()) != u'': self.P.wFunc = map(float, str(self.wFunc_lineEdit.text()).split(','))

                # Read weights from tableWidget into list
                self.P.wFunc = self.readwFuncTableEntries()
                
                # Convert one-element list to float. It is the amplitude of Ming-Yu sinusoidal function.
                if len(self.P.wFunc) == 1: self.P.wFunc = self.P.wFunc[0]
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique'
            self.P.nuqTech = unicode(self.nuqTech_comboBox.currentText())
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique / kS21+kS11 / K11'
            if unicode(self.nuqK11c1_lineEdit.text()) != u'' or self.P.synthTech == u'Prescribed Insertion Loss' and self.P.nuqTech == 'kS21+kS11': 
                self.P.nuqK11c1 = float(self.nuqK11c1_lineEdit.text())
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique / kS21+kS11 / K22'
            if unicode(self.nuqK22c1_lineEdit.text()) != u'' or self.P.synthTech == u'Prescribed Insertion Loss' and self.P.nuqTech == 'kS21+kS11': 
                self.P.nuqK22c1 = float(self.nuqK22c1_lineEdit.text())
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique / kS21+kS11 / K21'
            if unicode(self.nuqK21c1_lineEdit.text()) != u'' or self.P.synthTech == u'Prescribed Insertion Loss' and self.P.nuqTech == 'kS21+kS11': 
                self.P.nuqK21c1 = float(self.nuqK21c1_lineEdit.text())
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique / kS21 / Insertion losses K21'
            if unicode(self.nuqK21c2_lineEdit.text()) != u'' or self.P.synthTech == u'Prescribed Insertion Loss' and self.P.nuqTech == 'kS21': 
                self.P.nuqK21c2 = float(self.nuqK21c2_lineEdit.text())
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique / kS21+pole/zero / Insertion losses K21'
            if unicode(self.nuqK21c3_lineEdit.text()) != u'' or self.P.synthTech == u'Prescribed Insertion Loss' and self.P.nuqTech == 'kS21+pole/zero': 
                self.P.nuqK21c3 = float(self.nuqK21c3_lineEdit.text())
            
            key = 'Synthesis / Non-uniform Q / Prescribed Insertion Loss technique / kS21+pole/zero / Delta'
            if unicode(self.nuqDelta_lineEdit.text()) != u'' or (self.P.synthTech == u'Prescribed Insertion Loss' and self.P.nuqTech == 'kS21+pole/zero' ): 
                self.P.nuqDelta = float(self.nuqDelta_lineEdit.text())
            
            key = 'Sweep / Minimum Frequency'
            self.P.minFreq = float(self.minFreq_lineEdit.text()) * 10**(3*self.minFreqUnitsIndex)

            key = 'Sweep / Maximum Frequency'
            self.P.maxFreq = float(self.maxFreq_lineEdit.text()) * 10**(3*self.maxFreqUnitsIndex)
            
            key = 'Sweep / Number of samples'
            self.P.numFreq = self.numFreq_spinBox.value()
            
            if self.oldSweep != (self.P.minFreq, self.P.maxFreq): self.mainWindow.autoScaleFreq = True
            else: self.mainWindow.autoScaleFreq = False
            
            self.oldSweep = (self.P.minFreq, self.P.maxFreq)
            
        except ValueError,  err: 
            QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect parameter error:<br>%s<br>%s<br></p><p align="center">Fix or click "Discard" button.</p>' % (key, err))
            return False
        
        try:
            self.P.validate()
            
        except parseError,  err:
            QMessageBox.critical(self,  "ERROR", '<p align="center">Incorrect parameter error:<br>%s</p><p align="center">Fix or click "Discard" button.</p>' % err)
            return False
            
        # Recompute FT with updated filter parameters
        self.mainWindow.FT = FrequencyTransformBP(self.P)
        
        # Only at the end P can be considered non-empty
        self.P.empty = False
        
        
        return True
        
        
    ##@{
    # @name  Functions automatically excuted when the user interacts with the GUI 
    
    def updatePredisZerosArrang(self,  N):
        """
        Update the size of the predistortion zeros arrangement list after the user changes the filter order.
        @param N = Filter order as in self.N_spinBox.value()
        """
        #N = self.N_spinBox.value()
        if type(self.P.zerosArrang).__name__ == 'list':
            Nold = len(self.P.zerosArrang)
            if N > Nold: self.P.zerosArrang += [ False for n in range(N-Nold)]
            if N < Nold: self.P.zerosArrang = self.P.zerosArrang[0:N]
    
    
    def updatewFuncTableSize(self,  list,  dims,  updateEntries):
        """
        Make the size of the adaptive predistorsion weights table equal to the order of the filter.
        Calls self.updatewFuncTableEntries() function and adjusts widget size to table contents.
        This function is automatically executed by the GUI every time the user changes the filter order in the GUI or activates the adaptive predistiortion checkbox,
        but only if the synthesis method is 'Predistortion' and adaptive predistortion group box is checked.
        and is called by the self.updateDialogParams() function.
            @param list  = list containing entries to set in tableWidget
            @param dims  = tuple (rows, cols) contaning number of rows and columns
            @param updateEntries = If True, table entries are updated (calls  self.updatewFuncTableEntries(list)). 
                                   When this function is called by itemChanged signal, this flag must be False to avoid infinite recursion.
        """
        if unicode(self.synthTech_comboBox.currentText()) == u'Predistortion' and self.adapPredis_groupBox.isChecked():
            
            if updateEntries: 
                M = dims[0] # Filter order
                N = dims[1] # Equal to one column
                assert N==1
                if len(list) == 0:
                    list = (1 - 0.5 * np.sin(np.arange(1., M+1)*pi/(M+1)) )
                self.wFunc_tableWidget.setRowCount(M)
                self.wFunc_tableWidget.setColumnCount(N)
                self.updatewFuncTableEntries(list)
            
            self.wFunc_tableWidget.resizeRowsToContents()
            self.wFunc_tableWidget.resizeColumnsToContents()
            self.wFunc_tableWidget.setMaximumSize( self.wFunc_tableWidget.maximumSizeHint() )
        
        
    def updateTotaltransmissionZeros(self):
        """
        Count the number of Generalized Chebyshev transmission zeros set in the GUI widgets and update the lcdNumber widget.
        The dialog window is automatically resized to fit the rows corresponding to TZs.
        This function is automatically executed by the GUI every time the user changes one of the zeros lists in the GUI.
        """
        
        self.P.transImagZeros,  self.P.transComplexZeros,  transRealZeros = self.readTZsTableEntries(self.genChebyTZs_tableWidget)
        LPimagZeros,  self.P.LPcomplexZeros,  self.P.LPrealZeros = self.readTZsTableEntries(self.LPcomplexZeros_tableWidget)
        
        totalZeros = len(self.P.transImagZeros) + 2*len(self.P.transComplexZeros)
       
        if self.genChebyLP_groupBox.isChecked():  
            totalZeros += len(LPimagZeros) + 2*len(self.P.LPcomplexZeros + self.P.LPrealZeros)
            
        self.transmissionZeros_lcdNumber.display(totalZeros)
        
        # Update dialog size to fit table
        self.updateDlgHeight()
        
        
    def updateDlgHeight(self):
        """
        Update dialog size to fit table
        """
        
        incTZsTable = (self.genChebyTZs_tableWidget.rowCount()+1.5) * self.genChebyTZs_tableWidget.rowHeight(0)
        incLPzerosTable  = (self.LPcomplexZeros_tableWidget.rowCount()+1.5) * self.LPcomplexZeros_tableWidget.rowHeight(0)
        
        dlgSize = self.size()
        dlgHeight= dlgSize.height()
        dlgMinHeight = self.dlgInitSize
        dlgOptSize = dlgMinHeight + incTZsTable + incLPzerosTable
#        print 'incTZs =',  incTZsTable,  '/ incLP =',  incLPzerosTable, '/ Height =',  dlgHeight,  '/ MinHeight =',  dlgMinHeight,  '/ OptSize =',  dlgOptSize
#        print  'TZ Row =',  self.genChebyTZs_tableWidget.rowHeight(0),  '/ LP Row =',  self.LPcomplexZeros_tableWidget.rowHeight(1)
#        print 'TZscrollH = ',  self.genChebyTZs_tableWidget.horizontalScrollBar().height(),  '/ LPscrollH = ',  self.LPcomplexZeros_tableWidget.horizontalScrollBar().height()
#        print ''
        #if dlgHeight < dlgOptSize: 
        dlgSize.setHeight(dlgOptSize)
        self.resize(dlgSize)
        
    
    def dialogChanged(self):
        """
        Custom slot in Qt designer conected to widgets "changed" signal.
        This function is automatically executed by the GUI every time the user changes dialog values,
        and sets the self.dialogModified variable to True and self.recomputed to False.
        """
        self.dialogModified = True
        self.recomputed = False
        if hasattr(self.sender(),  'validator'):
            validator = self.sender().validator()
            if validator is not None: 
                result = validator.validate(self.sender().text(),  0)[0]
                palette = self.sender().palette()
                if result == 1: 
                    palette.setColor(QPalette.Text,  Qt.red)
                    self.sender().setPalette(palette)
                    self.badInput.append(self.sender()) 
                    self.sender().setFocus(Qt.OtherFocusReason)    # It does nothing, because it is executed before focus changes to another widget.
                if result == 2: 
                    palette.setColor(QPalette.Text,  self.mainWindow.palette().text().color())
                    self.sender().setPalette(palette)
                    while self.sender() in self.badInput: self.badInput.remove(self.sender()) 
        
        # For debugging
        #print [ unicode(n.text()) for n in self.badInput]
        
        if len(self.badInput) > 0:
            self.compute_pushButton.setEnabled(False)
            self.accept_pushButton.setEnabled(False)
        else:
            self.compute_pushButton.setEnabled(True)
            self.accept_pushButton.setEnabled(True)
            
        
    @pyqtSignature("")
    def on_outDir_pushButton_clicked(self):
        """
        Opens getExistingDirectory Qt dialog in order to set P.outDir variable.
        """
        outDir = self.P.outDir if self.P.outDir is not None else os.path.dirname(sys.argv[0])
        self.P.outDir = QFileDialog.getExistingDirectory(self, QString(u"Output directory"), outDir)
        if self.P.outDir:
            self.outDir_lineEdit.setText(QDir.toNativeSeparators(self.P.outDir))    
            
    
    @pyqtSignature("bool")
    def on_TZoptim_pushButton_clicked(self,  checked):        
        """
        Run imaginary TZ and bandwidth optimization to comply specification mask. 
        """
        
        # Read parameters in order to update self.P and update self.mainWindow.FT. The updated parameters can still be discarded with the 'Discard' pushbutton.
        if self.readDialogParams() is False:
            QMessageBox.critical(self, "ERROR", 'Incorrect parameter in edit window.<p>Cannot run optimization with incorrect parameters.')
            return

        algor = self.algor_comboBox.currentText()
        maxIter = self.maxIter_spinBox.value()
        Nsamples = self.Nsamples_spinBox.value()
        BWmaxvar = self.BWoptim_doubleSpinBox.value()
        weights = [ self.wS11_doubleSpinBox.value(),  self.wS22_doubleSpinBox.value(), self.wS21_doubleSpinBox.value() ]
        
        SM = self.mainWindow.SM
                    
        if SM is None:
            reply = QMessageBox.question(self, "WARNING",  '<b>No specification mask loaded.</b><p>Do you want to open an specification mask file now?', QMessageBox.Yes| QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                self.mainWindow.on_action_Mask_triggered(True)
                SM = self.mainWindow.SM
        
        runOptimization = False
        if SM is not None:
            if self.genChebyLP_groupBox.isChecked():
                reply = QMessageBox.question(self, "WARNING",  '''<b>Linear phase optimization is enabled.</b><p>
                            First, equalization zeros will be optimized for linear phase.<p>
                            Second, imaginary transmission zeros will be optimized to comply mask, with the optimum equalization zeros as prescribed transmission zeros.<p>
                            Third, if you have an open [S] parameter window, filter synthesis will be recomputed, which involves a new linear phase optimization with fixed prescribed transmission zeros.<p>
                            Do you wish to proceed?''', QMessageBox.Yes| QMessageBox.Cancel)
                if reply == QMessageBox.Yes: 
                    runOptimization = True
                    LPeqZeros = True
            else:
                runOptimization = True
                LPeqZeros = False
            
        if runOptimization:
            
            # First check if library libextrafilters.py has been imported
            if importedExtraFilters is False:
                if existingExtraFilters is False:
                    criticalErrorMsg("""<b>ERROR in Filter Synthesis:</b><p>
                                Chebyshev filter is in the Library of Extra Filters,<br>
                                which is not installed in this system. 
                                <p>Contact lossyfilters@tsc.upc.edu to get the Library of Extra Filters.
                            """)
                else:
                    criticalErrorMsg("""<b>ERROR in Filter Synthesis:</b><p>
                                Importing the 'Library of Extra Filters' failed. <br>
                                You have a libextrafilters.pyc file, but it cannot be imported. <br>
                                Possibly libextrafilters.pyc was compiled using a different Python version than yours.
                                <p>Your installed Python version is: %s on %s
                                <p>Read the README.html documentation or contact lossyfilters@tsc.upc.edu for assistence.
                            """ % (platform.python_version(),  platform.system()) )
                # Return without running optimization
                return
            
            app.setOverrideCursor(Qt.WaitCursor)
            try:
                TZoptimMask(self, self.P, self.mainWindow.FT, weights, BWmaxvar, algor, maxIter, Nsamples, SM, LPeqZeros)
            except synthError,  err:
                criticalErrorMsg('<b>ERROR in Filter Synthesis:</b><p>' + unicode(err))
                
            app.restoreOverrideCursor()
            myPrint('Imaginaty TZs and BW optimization to mask with parameters: alg=%s, maxIter=%d, Nsamples=%d, BWmaxvar=%g, w=%s, mask=%s' % (algor, maxIter, Nsamples, BWmaxvar, listStr(weights, 2), SM.fileName) )            
        else:
            myPrint('Imaginaty TZs and BW optimization to mask cancelled by user')
    
    
    @pyqtSignature("bool")
    def on_compute_pushButton_clicked(self,  checked):        
        """
        Run synthesis. If parameters are valid, self.mainWindow.on_action_Execute_triggered() will set self.recomputed to True.
        """
        # Launch synthesis from Main Window
        self.mainWindow.on_action_Execute_triggered(True)
            
            
    @pyqtSignature("bool")
    def on_TZS_newZero_pushButton_clicked(self,  checked):        
        """
        Add new zero (blank) to transmission zeros table.
        """
        self.genChebyTZs_tableWidget.addRow()
            
            
    @pyqtSignature("bool")
    def on_LP_newZero_pushButton_clicked(self,  checked):        
        """
        Add new zero (blank) to LP complex zeros table.
        """
        self.LPcomplexZeros_tableWidget.addRow()


    @pyqtSignature("bool")
    def on_TZs_symm_pushButton_clicked(self,  checked):        
        """
        Add new zero with symmetrical imaginary part (in normalized frequency) to transmission zeros table.
        """
        table = self.genChebyTZs_tableWidget
        row = table.currentRow()  
        
        if row == -1:
            stError = '<p align="center">Please select a transmission zero to symmetrize<p align="center"</p>'
            QMessageBox.warning(self,  "WARNING", stError) 
            return
        
        reItem = table.item(row, 0) 
        imItem = table.item(row, 1) 
        stRe = str(reItem.text()).strip()  if reItem is not None else '' 
        stIm = str(imItem.text()).strip() if imItem is not None else '' 
        re = float(stRe) if stRe != '' else 0
        im = float(stIm) * pow(10,  3*self.transmissionZerosUnitsIndex) if stIm != '' else 0
        
        # This does not work in case f0_lineEdit has changed, if you do not update FT first
        #imSym = self.mainWindow.FT.unormFreq( - self.mainWindow.FT.normFreq(im) ) 
        f0 = float(self.f0_lineEdit.text()) * 10**(3*self.f0UnitsIndex)
        imSym = f0**2 / im
        
        N = table.rowCount()
        table.setRowCount(N+1)
        imSymItem = QTableWidgetItem( '%.*g' % (saveSignificantDigits, imSym / pow(10,  3*self.transmissionZerosUnitsIndex)) )
        imSymItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)                            
        table.setItem(N, 1, imSymItem)
        reSymItem = QTableWidgetItem( '%.*g' % (saveSignificantDigits, re) )   if re != 0 else QTableWidgetItem('') 
        reSymItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)                            
        table.setItem(N, 0, reSymItem)
        
        table.updateTableSize()
        self.updateTotaltransmissionZeros()
        
        
    @pyqtSignature("bool")
    def on_LP_symm_pushButton_clicked(self,  checked):        
        """
        Add new zero with symmetrical imaginary part (in normalized frequency) to transmission zeros table.
        """
        table = self.LPcomplexZeros_tableWidget
        row = table.currentRow()  
        
        if row == -1:
            stError = '<p align="center">Please select a transmission zero to symmetrize<p align="center"</p>'
            QMessageBox.warning(self,  "WARNING", stError) 
            return
        
        reItem = table.item(row, 0) 
        imItem = table.item(row, 1) 
        stRe = str(reItem.text()).strip()  if reItem is not None else ''
        stIm = str(imItem.text()).strip()  if imItem is not None else ''
        re = float(stRe)  if stRe != '' else 0
        im = float(stIm) * pow(10,  3*self.transmissionZerosUnitsIndex)  if stIm != '' else 0
        
        imSym = self.mainWindow.FT.unormFreq( - self.mainWindow.FT.normFreq(im) )
        
        N = table.rowCount()
        table.setRowCount(N+1)
        imSymItem = QTableWidgetItem( '%.*g' % (saveSignificantDigits, imSym / pow(10,  3*self.transmissionZerosUnitsIndex)) )
        imSymItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)                            
        table.setItem(N, 1, imSymItem)
        reSymItem = QTableWidgetItem( '%.*g' % (saveSignificantDigits, re) )   if re != 0 else QTableWidgetItem('') 
        reSymItem.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)                            
        table.setItem(N, 0, reSymItem)
        
        table.updateTableSize()
        self.updateTotaltransmissionZeros()        
        
    
    def closeEvent(self,  event):
        """
        Save current dialog parameters to mainWindow.P and close dialog.
        This function is automatically executed by the GUI when the user clickes the 'Accept' button or closes the dialog.
        Closing the dialog by killing the window is equivalent to clicking the 'Accept' button.
        """
        if self.rejected:
            event.accept()
        else:
            # Copy dialog parameters to mainWindow.P only if there is no incorrect parameter
            if self.readDialogParams() is True: 
                
                if self.dialogModified:                    
                    if not self.recomputed: 
                        message = "Parameters have been modified and it is necessary to recompute synthesis.<p>Continue?"
                        reply = QMessageBox.question(self, "%s - Recompute" % applicationName, message, QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
                        if reply in [QMessageBox.Cancel,  QMessageBox.No]:
                            event.ignore()
                            return
                        elif reply == QMessageBox.Yes:
                            self.on_compute_pushButton_clicked(True)
                    # If parameters have been modified and closeEvent is not ignored, mainWindow.dirty must be True
                    self.mainWindow.dirty = True
                    self.mainWindow.setWindowModified(self.mainWindow.dirty)
                        
                self.mainWindow.updateStatus("Parameter values accepted")
                self.mainWindow.paramEdit = None
                event.accept()
            else:
                self.mainWindow.updateStatus("Incorrect parameters not accepted")
                event.ignore()
        
    
    @pyqtSignature("bool")
    def on_discard_pushButton_clicked(self,  checked):  
        """
        Reject current dialog parameters and close dialog.
        A backup copy of parameters and synthesis results is recovered.
        This function is automatically executed by the GUI when the user clickes the 'Discard' button.
        """
        if self.dialogModified or self.recomputed:
            message = """Dialog discarded by user:<p>
                                All parameters and synthesis results will be reverted to a previous state. To keep your parameters and results, click the "Accept" button in the Parameter Editor.<p>
                                Revert parameters and results?"""
            reply = QMessageBox.question(self, "%s - Discard parameters" % applicationName, message, QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
            if reply in [QMessageBox.Cancel,  QMessageBox.No]:
                return
                
        (self.mainWindow.P,  self.mainWindow.CP,  self.mainWindow.CM,  self.mainWindow.SP,  self.mainWindow.FT) = copy.deepcopy(self.resultBkp)
        self.mainWindow.updateStatus("Parameter values discarded by user")
        self.mainWindow.paramEdit = None
        self.mainWindow.updateWindows()         # After setting self.mainWindow.paramEdit = None, to avoid updating paramEdit unnecessarily-
        self.rejected = True
        self.close()
        
        # reject() only works if window is a QDialog.
        #self.reject()
            
    
    @pyqtSignature("int")
    def on_f0_comboBox_currentIndexChanged(self,  value):
        """
        Update lineEdit value when the units ComboBox is changed.
        This function is automatically executed by the GUI when the user changes the filter frequency units ComboBox.
        """
        #newIndex = self.f0_comboBox.currentIndex()
        newIndex = value
        if self.f0UnitsIndex is not None:   # If it is None, the dialog has just been created and we set units for the 1st time
            newValue = float(self.f0_lineEdit.text()) * pow(10,  3*(self.f0UnitsIndex-newIndex))
            self.f0_lineEdit.setText(str(newValue))
        self.f0UnitsIndex = newIndex
    
    
    @pyqtSignature("int")
    def on_BW_comboBox_currentIndexChanged(self,  value):
        """
        Update lineEdit value when the units ComboBox is changed.
        This function is automatically executed by the GUI when the user changes the filter bandwidht units ComboBox.
        """
        #newIndex = self.BW_comboBox.currentIndex()
        newIndex = value
        if self.BWUnitsIndex is not None:   # If it is None, the dialog has just been created and we set units for the 1st time
            newValue = float(self.BW_lineEdit.text()) * pow(10,  3*(self.BWUnitsIndex-newIndex))
            self.BW_lineEdit.setText(str(newValue))
        self.BWUnitsIndex = newIndex        
            
    
    @pyqtSignature("int")
    def on_qeZero_comboBox_currentIndexChanged(self,  value):
        """
        Update lineEdit value when the units ComboBox is changed.
        This function is automatically executed by the GUI when the user changes the Quasieliptic zeros frequency units ComboBox.
        """
        #newIndex = self.qeZero_comboBox.currentIndex()
        newIndex = value
        if self.qeZeroUnitsIndex is not None:   # If it is None, the dialog has just been created and we set units for the 1st time
            newValue = float(self.qeZero_lineEdit.text()) * pow(10,  3*(self.qeZeroUnitsIndex-newIndex))
            self.qeZero_lineEdit.setText(str(newValue))
        self.qeZeroUnitsIndex = newIndex
            
    
    @pyqtSignature("int")
    def on_transmissionZeros_comboBox_currentIndexChanged(self,  value):
        """
        Update lineEdit value when the units ComboBox is changed.
        This function is automatically executed by the GUI when the user changes the Generalized Chebyshev zeros frequency units ComboBox.
        """
#        self.transmissionZerosUnitsIndex = self.transmissionZeros_comboBox.currentIndex()
        self.transmissionZerosUnitsIndex = value
        
        # Update table Widgets using new self.transmissionZerosUnitsIndex
        TZsList = np.concatenate( (1j*np.array(self.P.transImagZeros),  np.array(self.P.transComplexZeros)) ).tolist()
        self.updateTZsTable(self.genChebyTZs_tableWidget, TZsList)
        self.updateTZsTable(self.LPcomplexZeros_tableWidget,  self.P.LPcomplexZeros + self.P.LPrealZeros)
        
        
    @pyqtSignature("int")
    def on_minFreq_comboBox_currentIndexChanged(self,  value):
        """
        Update lineEdit value when the units ComboBox is changed.
        This function is automatically executed by the GUI when the user changes the minimum sweep frequency units ComboBox.
        """
        #newIndex = self.minFreq_comboBox.currentIndex()
        newIndex = value
        if self.minFreqUnitsIndex is not None:   # If it is None, the dialog has just been created and we set units for the 1st time
            newValue = float(self.minFreq_lineEdit.text()) * pow(10,  3*(self.minFreqUnitsIndex-newIndex))
            self.minFreq_lineEdit.setText(str(newValue))
        self.minFreqUnitsIndex = newIndex
        
        
    @pyqtSignature("int")
    def on_maxFreq_comboBox_currentIndexChanged(self,  value):
        """
        Update lineEdit value when the units ComboBox is changed.
        This function is automatically executed by the GUI when the user changes the maximum sweep frequency units ComboBox.
        """
        #newIndex = self.maxFreq_comboBox.currentIndex()        
        newIndex = value
        if self.maxFreqUnitsIndex is not None:   # If it is None, the dialog has just been created and we set units for the 1st time
            newValue = float(self.maxFreq_lineEdit.text()) * pow(10,  3*(self.maxFreqUnitsIndex-newIndex))
            self.maxFreq_lineEdit.setText(str(newValue))
        self.maxFreqUnitsIndex = newIndex
        
    ##@}

# End class:  ParamEditDlg  #
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#

# !!!!!!!!!!!!!!!!!!!!!!!#
# Global functions:  #

def runSynthesis(P, FT, symmetrizeZeros):
    """
    Runs parameter validation, filters synthesis and results plots.
        @param P = Parameters class instance containing the parameter values that will be used in the computation. May be modified during computation.
        @param FT = FrequencyTransform class instance.
        @param symmetrizeZeros = Automatically symmetrize Generalized Chebyshev zeros in Hz in order to compute a folded matrix with uniform Q in resonators. 
                                                     Equivalent to answering "Yes" in the GUI when it asks the user if he wants to symmetrize Generalized Chebyshev zeros in Hz.
        @return CP = CharPolys class instance, containing Characteristic Polynomials. Losses are included.  
        @return CM = CouplingMatrices class instance, containing Coupling Matrices. Losses are included. 
        @return SP = Sparameters class instance, containing frequency axis and [S] data. Losses are included. 
        @return done = True if Synthesis has been done without errors, False otherwise.
    """	
    CP,  CM,  SP = None, None, None
    
    try:
        
        # Compute characteristic polynomials
        CP = CharPolys(P,  FT,  symmetrizeZeros)
        myPrint("Relative error in the computation of lossless characteristic polynomials = %g" % CP.rootsErr)
        
        if P.synthTech.title() == u'Predistortion':
            if importedLossyFilters is False:
                if existingLossyFilters is False:
                    raise synthError,  """Pre-distorted characteristic polynomials are in the Library of Lossy Filters, which is not installed in this system. 
                            <p>Contact lossyfilters@tsc.upc.edu to get the Library of Lossy Filters.
                           """
                else:
                    raise synthError,  """Importing the 'Library of Lossy Filters' failed. <br>
                                You have a liblossyfilters.pyc file, but it cannot be imported. <br>
                                Possibly liblossyfilters.pyc was compiled using a different Python version than yours.
                                <p>Your installed Python version is: %s on %s
                                <p>Read the README.html documentation or contact lossyfilters@tsc.upc.edu for assistence.
                            """ % (platform.python_version(),  platform.system())
            elif importedCheckLicense is False:
                if existingCheckLicense is False:
                    raise synthError,   """There is no check_license.pyc file in your system.
                                It should have been installed together with libextrafilters.pyc file, 
                                which actually is in your system.
                                Please follow the 'Extra Filters Library' installation instructions.
                             """
                else:
                    raise synthError,  """Importing the 'License Check Library' failed. <br>
                                You have a libchecklicense.pyc file, but it cannot be imported. <br>
                                Possibly libchecklicense.pyc was compiled using a different Python version than yours.
                                <p>Your installed Python version is: %s on %s
                                <p>Read the README.html documentation or contact lossyfilters@tsc.upc.edu for assistence.
                            """ % (platform.python_version(),  platform.system())
            else:
                predistortion(P,  CP,  FT )
                myPrint("Relative error in the computation of pre-distorted characteristic polynomials = %g" % CP.rootsErr)

        elif  P.synthTech.title() == u'Prescribed Insertion Loss':
            if importedLossyFilters is False:
                if existingLossyFilters is False:
                    raise synthError,  """Prescribed insertion loss characteristic polynomials are in the Library of Lossy Filters, which is not installed in this system. 
                            <p>Contact lossyfilters@tsc.upc.edu to get the Library of Lossy Filters.
                           """
                else:
                    raise synthError,  """Importing the 'Library of Lossy Filters' failed. <br>
                                You have a liblossyfilters.pyc file, but it cannot be imported. <br>
                                Possibly liblossyfilters.pyc was compiled using a different Python version than yours.
                                <p>Your installed Python version is: %s on %s
                                <p>Read the README.html documentation or contact lossyfilters@tsc.upc.edu for assistence.
                            """ % (platform.python_version(),  platform.system())
            elif importedCheckLicense is False:
                if existingCheckLicense is False:
                    raise synthError,   """There is no check_license.pyc file in your system.
                                It should have been installed together with libextrafilters.pyc file, 
                                which actually is in your system.
                                Please follow the 'Extra Filters Library' installation instructions.
                             """
                else:
                    raise synthError,  """Importing the 'License Check Library' failed. <br>
                                You have a libchecklicense.pyc file, but it cannot be imported. <br>
                                Possibly libchecklicense.pyc was compiled using a different Python version than yours.
                                <p>Your installed Python version is: %s on %s
                                <p>Read the README.html documentation or contact lossyfilters@tsc.upc.edu for assistence.
                            """ % (platform.python_version(),  platform.system())
            else:
                nonUniformQ(P,  CP)
        
        # Compute [S] parameters from characteristic polynomials
        SP = Sparameters(P, CP,  FT)
        
        # Compute coupling matrix
        Yo = 1          # Normalized load
        CM = CouplingMatrices(P,  CP, Yo,  FT,  SP)
        myPrint("Relative error in the computation of transversal coupling matrix = %g" % CM.CM_error) 
        
        # Compute error between [S] from coupling matrices and [S] from characteristic polynomials        
        # Do not compute it in the case 'kS21+pole/zero', since the node corresponding to the added pole is removed from the CM, resulting in large errors.
        if (P.synthTech.title() != u'Prescribed Insertion Loss') or (P.nuqTech != u'kS21+pole/zero'):
#            for MatQ in CM.listM:
            for MatQ in [ CM.listM[0] ]:    # It is slow to compute [S] for all the MatQ, do it only for the 1st one (TCM) just to estimate the error in CM
                myPrint(MatQ.name)
                stErrorCM, CM_error = SP.fromCouplingMatrix(MatQ)
                myPrint(stErrorCM + ', Relative error: %e\n' % CM_error)
                CM.updateCM_error(CM_error, st = 'Error in %s' % MatQ.name)

        if P.outDir is not None: 
            # Normalize and expand output directory name, just in case
            P.outDir = os.path.normpath(P.outDir)
            P.outDir = os.path.expanduser(P.outDir)
            
            # Check if directory exists
            if not os.path.isdir(P.outDir): 
                P.outDirName = None
                warningMsg(u"WARNING: Directory '%s' does not exist. <p>Results not saved" % P.outDir)
            else:
                # Create output name using outDir and outName
                P.outDirName =  os.path.join(P.outDir,  P.outName)
        else:                                       
            P.outDirName =  P.outName
                
        if P.outDirName is not None:
            # Normalize output path just in case 
            P.outDirName = os.path.normpath(P.outDirName)    
    
            # Save everything
            CP.saveCharPolys(P,  saveSignificantDigits)
            SP.saveSparam(P,  saveSignificantDigits)
            if saveDefaultMatrices:
                CM.saveCouplingMatrices(P, SP, saveSignificantDigits, saveSignificantDigitsEnergy)
        
    except licenseError,  err:
        criticalErrorMsg('License ERROR: <p>' + unicode(err))
        return None,  None,  None,  False
    
    except synthError,  err:
        criticalErrorMsg('<b>ERROR in Filter Synthesis:</b><p>' + unicode(err))
        return None,  None,  None,  False
            
    return CP,  CM,  SP,  True
    

def usage(st,  exit_code):
    """
    Print error message and command line help.
        @param st = Error message string.
        @param exit_code = program exit return code.
    """
   
    pgrm_name = os.path.basename(sys.argv[0])
    print st
    print 'usage: ' + pgrm_name + ' [-vh] [--help] [parameter_file]'
    print '\nOptions:'
    print '-v: Verbose output. Show more information (for debugging) at the log in the main window (or at the console with --nogui flag).'
    print '-s: Automatically symmetrize Generalized Chebyshev zeros in Hz in order to compute a folded matrix with uniform Q in resonators.'    
    print '-r: Automatically run Synthesis. Needs simultaneous parameter file specification.'
    print '-m: Automatically open Coupling Matrices window. Needs simultaneous -r flag.'
    print '-e: Automatically open Matrix Edit window. Needs simultaneous -m flag.'
    print '-p N: Save all default coupling matrices to file after they are computed, using N significant digits. By default the matrices must be saved manually using the GUI.'
    print '-h, --help: help'
    print '--nogui: Command line interface'
    print '--style windows, motif, cleanlooks, plastique or cde'
    print '\nArguments:'
    print 'parameter_file (optional): Path to ASCII file containing parameter values\n'
    sys.exit(exit_code)

# End: Global functions:  #
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!#


# !!!!!!!!!!!!!!!!!!!!!!!#
# MAIN PROGRAM #

if __name__ == '__main__':
    """
    Main program
    """
    
    ##
    # Program name as run by the user.
    applicationName = "Microwave filters GUI"
    
    # Check input arguments syntax
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hsvrmep:", ["style=",  "help",  "nogui"])
    except getopt.GetoptError:
        # print help information and exit:
        usage('Error: Bad command line parameters',  2)

    QtArgs = list()
    QtArgs.append(sys.argv[0])
    
    # Initialize command line flags class
    symmetrizeZeros = None
    verbose = autoRun = autoMatrix = autoEditMatrix = False
    QtArgsStyle = None
    nogui = False
    saveDefaultMatrices = False
    
    for o, a in opts:
        if o == "-v":                     
            verbose = True
            
        if o == "-s":
            symmetrizeZeros = True
            
        if o == "-r":
            autoRun = True
            
        if o == "-m":
            autoMatrix = True
            
        if o == "-e":
            autoEditMatrix = True
            
        if o == "-p":
            try:
                saveSignificantDigits = int(a)
            except ValueError: 
                print "ERROR in command line flags:"
                print "Precision flag -p must take a numeric argument."
                print "To get help use -h flag:\n%s -h\n" % sys.argv[0]
                sys.exit(0)
            saveDefaultMatrices = True
            
        if o in ("-h", "--help"):   
            usage('Help:',  0)
            
        if o == "--nogui":
            nogui = True
            
        if o in ["--style"]:           
            QtArgsStyle = a
            
    setGlobalVariablesLibCommonFunc(verbose = verbose,  nogui = nogui)
    
    printHeader( applicationName,  __doc__)
    
#    myPrint('Significant digits to use when saving coupling matrices: %d' % saveSignificantDigits)
    
    if nogui: # Command line interface
        if len(args) != 1:                   usage('Error: Incorrect number of arguments',  2)
        
        ##
        # Name of the parameters file to process
        fileName = args[0]
        
        ##
        # Parameters class instance containing filter and synthesis parameters read from the parameters file.
        P = readParamFile(fileName)
        
        ##
        # Create frequency transform instance
        FT = FrequencyTransformBP(P)
        
        # Execute the synthesis
        CP,  CM,  SP,  done = runSynthesis(P, FT, symmetrizeZeros)    
    
    else: # Graphical User Interface (GUI)
        if QtArgsStyle is None: QtArgsStyle = 'cleanlooks'
        QtArgs.extend(['-style', QtArgsStyle ])

        if len(args) > 1:                    usage('Error: Incorrect number of arguments',  2)
        else:
            if len(args) == 1:             fileName = unicode(args[0])
            else:                                fileName = None       # Start with blank parameter values
            
        ##
        # Create validator strings and regular expressions. The are global variables to be accessible  from everywhere.
        stFloatPoint = r'((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?\d+)?)'
        stReal = r'(([-+]?)' + stFloatPoint + '\s*)'
        stRealPositive =  r'((\+?)' + stFloatPoint + '\s*)'
        stComplex = r'(([-+]?' + stFloatPoint + r'|[-+]?(' + stFloatPoint + r')?[ij]|[-+]?'  + stFloatPoint + r'\s*[-+]\s*('  + stFloatPoint + r')?[ij])\s*)'
        stRealList = stReal + r'(,\s*' + stReal + r')*'
        stRealPositiveList = stRealPositive + r'(,\s*' + stRealPositive + r')*'
        stComplexList = stComplex + r'(,\s*' + stComplex + r')*'
        
        reReal = QRegExp( stReal + r'|^$')
        reRealPositive = QRegExp( stRealPositive + r'|^$')
        reRealPositiveInf = QRegExp( stRealPositive + r'|^$|inf|Inf')
        reComplex = QRegExp( stComplex + r'|^$')
        reRealList = QRegExp( stRealList + r'|^$' )
        reRealPositiveList = QRegExp( stRealPositiveList + r'|^$' )
        reComplexList = QRegExp( stComplexList + r'|^$' )
        
        
        # Start user interface
        
        #QApplication.setStyle(QCleanlooksStyle())
        #QApplication.setStyle(QWindowsXPStyle())
    
        ##
        # QApplication class instance, containing our application
        app = QApplication(QtArgs)
        app.setOrganizationName("UPC")
        app.setOrganizationDomain("http://www.tsc.upc.edu/hfs; http://www.tsc.upc.edu/antennalab")
        app.setApplicationName(applicationName)
        app.setWindowIcon(QIcon(":/icon/icons/icon_Spar-5.png"))
        
        mainWindow = MainWindow(fileName)
        mainWindow.show()
        #mainWindow.activateWindow()    
        app.exec_()
        
