#!/usr/bin/python

import sys                
import numpy
from   Hurricane    import *
from   CRL          import *
import Constant
from   math         import sqrt, ceil
import helpers
from   helpers.io   import ErrorMessage as Error
from   helpers      import trace
import oroshi

def toDbU ( l ): return DbU.fromPhysical( l, DbU.UnitPowerMicro )
def toPhY ( l ): return DbU.toPhysical  ( l, DbU.UnitPowerMicro )

def doBreak( level, message ):
    UpdateSession.close()
    Breakpoint.stop( level, message )
    UpdateSession.open()


## Draws a capacitor of type Poly-Poly or Metal-Metal in 350 nm AMS CMOS technology. 
#  PIP and MIM capacitors are the result of surface superposition between poly1 and poly2 or metal2 and metalcap layers, respectively.
#  Given the capacitor value, layout dimensions are computed, then,  capacitor layers are drawn. Capacitor value, \f$C\f$, is given in the expression below, where \f$ C_{a}, C_{p}, A \f$ and \f$ P \f$ are, area capacitance, perimeter capacitance, area and permiter of the capacitor, respectively :
#  \f[ C = C_{a}A + C_{p}P \f]
#  The drawn layout shape is square. Thus, metcap or poly2 length and width are equal and are computed using the capacitor expression. Furthermore, given \f$ C_{a} \f$, \f$ C_{p} \f$ and enclosure technological rules, dimensions and positions of the abutment box as well as the bottom plate are computed. Layouts with dimensions that exceed technological limits cannot be drawn.

class CapacitorUnit():
    
    rules = oroshi.getRules()


    ## This is the class constructor. Few of the class attributes final values are computed in this level. Most of attributes are only initialized to zero or empty values. Then, it is computed in dedicated class method. Input parameters are :
    # \param   device                         Hurricane AMS device into which layout is drawn. 
    # \param   capacitance                    Capacitor value, expressed in \f$ femto Farad (fF) \f$.
    # \param   abutmentBoxPosition            A list containing abscissa and ordinate of the bottom left corner of the abutment box.
    #
    # Class attributes are described in the list below. Most of class attributes refer to layout dimensions. Dictionaries are used to group attributes related to the same layout varibale. Layout dimensions and variables are described in Figure 1. 
    #
    # \param   device                         Hurricane AMS device into which layout is drawn. 
    # \param   capacitance                    Capacitor value, expressed in \f$ femto Farad (fF) \f$.
    # \param   capacitorType                  Can be 'MIMCap' or 'PIPCap' as capacitor type. 
    # \param   abutmentBoxDict                A dictionary containing abscissa and ordinate of the bottom left corner of the abutment box, (XMin) and (YMin), respectively. 
    # \param   abutmentBox                       Abutment box drawn square. It is an object of type \c Box.               
    # \param   bottomPlateBox                 Bottom plate drawn square. It is an object of type \c Box.
    # \param   topPlateBox                    Top plate drawn square. It is an object of type \c Box.
    # \param   cut2MatrixDict                      A dictionary containing center position of the left bottom, which is cut the first to be drawn in the matrix of cuts. Initially, the dictionary is empty. It is only updated when \c self.capacitorType is equal to \c 'MIMCap'. 
    #
    # \param   cutLeftLineDict                A dictionary containing abcissa and ordinate of the bottom cut in the left line of cuts to be drawn on bottom plate's layer. 
    # \param   cutRightLineDict               A dictionary containing abcissa and ordinate of the bottom cut in the right line of cuts to be drawn on bottom plate's layer.
    # \param   topCutLineDict                 A dictionary containing abcissa and ordinate of the bottom cut in the right line of cuts to be drawn on top plate's layer. Initially, the dictionary is empty. It is only updated when \c self.capacitorType is equal to \c 'PIPCap'. 
    #
    # \param   topPlateRLayerDict             A dictionary containing position information of the top plate's routing layer. The dictionary includes ordinates of the layer's top and bottom extremities, \c 'XMin' and \c 'YMin', respectively, the abcissa of it's center, \c 'XCenter' and its width, \c 'width'. 
    #
    # \param   bottomPlateRLayerDict          A dictionary containing
    # \param   enclosure_botPlate_topPlate    Top plate's layer encolusre in bottom plate's layer.
    # \param   minheight_topPlatecut          Minimum height of cuts for top plate connection to other metal layer.
    # \param   topCutLineNumber               Maximum possible number cuts to be drawn for top plate's connection.
    # \param   bottomCutLineNumber            Maximum possible number cuts to be drawn for top plate's connection.
    # 
    # \remark  Abutment box must be defined as an attribute because the position of dummy capacitor in \c NonUnitCapacitor class must be precisely defined.

    def __init__( self, device, capacitorType, abutmentBoxPosition, capacitance = 0, capDim = {} ):
       
        self.device                      =   device
        self.capacitorType               =   capacitorType
#       self.capDim                      =   self.__computeCapDim__( capacitance, capacitorType ) 
        self.enclosure_botPlate_abtBox   =  0 
        
        self.__initCapDim__( capacitance, capDim )

        self.abutmentBoxDict             = { "XMin" : abutmentBoxPosition[0], "YMin" : abutmentBoxPosition[1] }
        self.abutmentBox                 =   Box ()
        self.bottomPlateBox              =   Box ()
        self.topPlateBox                 =   Box ()

        self.bottomPlateBoxDict          =   {}
        self.topPlateBoxDict             =   {}

        self.cut2MatrixDict              =   {}     
        self.cutLeftLineDict             =   {} 
        self.cutRightLineDict            =   {}
        self.topCutLineDict              =   {}       
        self.topPlateRLayerDict          =   {}
        self.bottomPlateRLayerDict       =   {}
        self.topCutLineNumber            =   {} 

        self.enclosure_botPlate_topPlate =   0
        self.minheight_topPlatecut       =   0
        self.bottomCutLineNumber         =   0

        return



    def __initCapDim__( self, capacitance, capDim ):
        if capacitance != 0 and capDim.values() != []:
          if self.__computeCapacitance__( capDim, self.capacitorType ) == capacitance :
            self.capDim = capDim
          else:
            raise Error( 1, [ 'CapacitorUnit.__initCapDim__(): Please check compatibility between capacitance value {0},'.format(capacitance)
                            , 'and the given capacitor dimensions {0}.'.format(capDim) ] )
        elif capacitance == 0 and capDim.values() != []: 
          self.capDim = capDim 
          capacitance = self.__computeCapacitance__( capDim, self.capacitorType )
        elif capacitance != 0 and capDim.values() == [] :
          self.capDim = self.__computeCapDim__( capacitance, self.capacitorType ) 
        else:
          raise Error( 1, [ 'CapacitorUnit.__initCapDim__(): Invalid capacitance and/or capacitance dimensions {0}, {1}.'.format(capacitance, capDim)
                          , 'Please provide at least one valid parameter.' ] )
        return


    ## Sets the area and perimeter capacitances as specified in 350 nm AMS technology and according to \c capacitorType (MIM or PIP).
    #  \return    a list containing the area and perimeter capacitances.
    #  \remarks   An exception is raised if the entered capacitor type is unknown.
  
    def __setCapacitorPerUnit__( self, capacitorType ) :
       #print("self.capacitorType",self.capacitorType)
        if   capacitorType == 'MIMCap':
            [ areaCapacitorPerUnit, perimeterCapacitorPerUnit ] = [ CapacitorUnit.rules.MIMCap
                                                                  , CapacitorUnit.rules.MIMPerimeterCap
                                                                  ]

        elif capacitorType == 'PIPCap':
            [ areaCapacitorPerUnit, perimeterCapacitorPerUnit ] = [ CapacitorUnit.rules.PIPCap
                                                                  , CapacitorUnit.rules.PIPPerimeterCap
                                                                  ]

        else:
            raise Error( 1, 'setCapacitorPerUnit() : Unsupported capacitor type : %s.' % capacitorType ) 

        return [ areaCapacitorPerUnit, perimeterCapacitorPerUnit ]


    ## Computes width and length of the capacitor. Given \c capacitance value as well as
    #  the permiter and area capacitances, a quadratic equation is solved where the
    #  unknown parameter is the width (also equivalent to the length).
    #  \return    a dictionary containing width and length.
    #  \remark    The capacitor is square. Thus, length and width are equal.

    def __computeCapDim__( self, capacitance, capacitorType ) : 
        trace( 101, ',+', '\tCapacitorUnit.__computeCapDim__()\n' )

        [a, b, c] = [     self.__setCapacitorPerUnit__( capacitorType )[0]
                    , 4 * self.__setCapacitorPerUnit__( capacitorType )[1]
                    ,   - capacitance
                    ]

        delta = (b**2) - 4*a*c
        c1    = ( -b + sqrt(delta) ) / (2*a)

        twoGrid = DbU.fromGrid( 2.0 )
        trace( 101, '\tc1 = {0}, 2*Grid Unit = {1}\n'.format( c1, twoGrid ))
        c1 = toDbU( c1 )
        c1 = c1 + (twoGrid - (c1 % twoGrid))

        trace( 101, '-' )
        return { "width" : c1, "height" : c1 }


    def __computeCapacitance__( self, capDim, capacitorType ):

        [ areaCapacitorPerUnit, perimeterCapacitorPerUnit ] = self.__setCapacitorPerUnit__( capacitorType )
        areaCapacitance      =   toPhY(capDim["width"]) * toPhY(capDim["height"])  *areaCapacitorPerUnit
        perimeterCapacitance = ( toPhY(capDim["width"]) + toPhY(capDim["height"]) )*perimeterCapacitorPerUnit
        capacitance          =   areaCapacitance        + perimeterCapacitance 

        return capacitance


    ## Checks if the computed capacitor dimensions exceed or are less than maximum and minimum limits, respectively, as specified in technology rules.
    ## \return   \c True if all rules are respected.
    #  \remark    Maximum poly2 layer dimensions for PIP capacitor are not specified in technology rules. Thus, only minimum limit condition is checked.

    def __isCapacitorUnitOK__( self, capDim ): 
        errors   = []
        capaName = 'PIP'
        minSide  = self.getMinimumCapWidth()
        maxSide  = None
        if self.capacitorType == 'MIMCap':
          capaName = 'MIM'
          maxSide  = self.getMaximumCapWidth()

        trace( 101, '\tcapDim = [ {0}, {1} ]\n'.format( DbU.getValueString(capDim['width' ])
                                                      , DbU.getValueString(capDim['height']) ))
        trace( 101, '\tCapacity type: {0}\n'.format( capaName ))
        trace( 101, '\t* Minimum side: {0}\n'.format( DbU.getValueString(minSide) ))
        if maxSide: trace( 101, '\t* Maximum side: {0}\n'.format( DbU.getValueString(maxSide) ))
        else:       trace( 101, '\t* Maximum side: N/A\n' )

        if capDim['width' ] < minSide:
          errors.append( '* Width {0} too small, minimum size for {2} capacitors is {1}.' \
                         .format( DbU.getValueString(capDim['width'])
                                , DbU.getValueString(minSide        )
                                , capaName  ) )
        if maxSide and capDim['width' ] > maxSide:
          errors.append( '* Width {0} too big, maximum size for {2} capacitors is {1}.' \
                         .format( DbU.getValueString(capDim['width'])
                                , DbU.getValueString(maxSide        )
                                , capaName  ) )

        if capDim['height' ] < minSide:
          errors.append( '* Height {0} too small, minimum size for {2} capacitors is {1}.' \
                         .format( DbU.getValueString(capDim['height'])
                                , DbU.getValueString(minSide        )
                                , capaName  ) )
        if maxSide and capDim['height' ] > maxSide:
          errors.append( '* Height {0} too big, maximum size for {2} capacitors is {1}.' \
                         .format( DbU.getValueString(capDim['height'])
                                , DbU.getValueString(maxSide        )
                                , capaName  ) )

        if errors:
          errors.insert( 0, 'CapacitorUnit.__isCapacitorUnitOK__():' )
          raise Error( 1, errors )

        return True


    ## Selects technological rules according to the capacitor type.     
    #  \return a dictionary with rules labels as keys and rules as values. 
    #  Example of technology rules are :
    #  - minimum spacing between cuts or metals,
    #  - minimum width of a plate, a cut or a routing metal.
    #  - etc.  
    #  Every rule takes two possible value according to the capacitor type (MIM or PIP).
    #  Therefore, dictionary keys are generic and its values are specific to the capacitor
    #  type.
    #  \remark   An exception is raised if the entered capacitor type is unknown.

    def setRules ( self ):
        if self.capacitorType == 'MIMCap':
          self.minWidth_topPlate                   = CapacitorUnit.rules.minWidth_metcap
          self.minWidth_botRMetal                  = CapacitorUnit.rules.minWidth_metal3
          self.minWidth_topRMetal                  = CapacitorUnit.rules.minWidth_metal1
          self.minWidth_topPlatecut                = CapacitorUnit.rules.minWidth_cut2
          self.minWidth_botPlatecut                = CapacitorUnit.rules.minWidth_cut2
          self.minWidth_routingTrackcut            = CapacitorUnit.rules.minWidth_cut2

          self.minSpacing_botPlate                 = CapacitorUnit.rules.minSpacing_metbot        
          self.minSpacing_botPlateCut_topPlate     = CapacitorUnit.rules.minSpacing_cut2_metcap
          self.minSpacingOnBotPlate_cut            = CapacitorUnit.rules.minSpacingOnMetBot_cut2
          self.minSpacingOnTopPlate_cut            = CapacitorUnit.rules.minSpacingOnMetCap_cut2

          self.minEnclo_botPlate_botPlateCut       = CapacitorUnit.rules.minEnclosure_metbot_cut2
          self.minEnclo_topPlate_topPlateCut       = CapacitorUnit.rules.minEnclosure_metcap_cut2
          self.minEnclo_topPlateRMetal_topPlateCut = CapacitorUnit.rules.minEnclosure_metal3_cut2
          self.minEnclo_botPlateRMetal_botPlateCut = CapacitorUnit.rules.minEnclosure_metal3_cut2
          self.minEnclo_routingTrackMetal_cut      = CapacitorUnit.rules.minEnclosure_metal3_cut2
        elif self.capacitorType == 'PIPCap':
          self.minWidth_topPlate                   = CapacitorUnit.rules.minWidth_cpoly
          self.minWidth_botRMetal                  = CapacitorUnit.rules.minWidth_metal1
          self.minWidth_topRMetal                  = CapacitorUnit.rules.minWidth_metal1
          self.minWidth_topPlatecut                = CapacitorUnit.rules.minWidth_cut0
          self.minWidth_botPlatecut                = CapacitorUnit.rules.minWidth_cut0
          self.minWidth_routingTrackcut            = CapacitorUnit.rules.minWidth_cut1

          self.minSpacing_botPlate                 = CapacitorUnit.rules.minSpacing_poly        
          self.minSpacing_botPlateCut_topPlate     = CapacitorUnit.rules.minSpacing_cut0_cpoly
          self.minSpacingOnBotPlate_cut            = CapacitorUnit.rules.minSpacing_cut0
          self.minSpacingOnTopPlate_cut            = CapacitorUnit.rules.minSpacing_cut0

          self.minEnclo_botPlate_botPlateCut       = CapacitorUnit.rules.minEnclosure_poly_cut0
          self.minEnclo_topPlate_topPlateCut       = CapacitorUnit.rules.minEnclosure_cpoly_cut0
          self.minEnclo_topPlateRMetal_topPlateCut = CapacitorUnit.rules.minEnclosure_metal1_cut1
          self.minEnclo_botPlateRMetal_botPlateCut = CapacitorUnit.rules.minEnclosure_metal1_cut0
          self.minEnclo_routingTrackMetal_cut      = CapacitorUnit.rules.minEnclosure_metal1_cut1
        else:
          raise Error( 1, 'CapacitorUnit.setRules(): Unsupported capacitor type {0}.'.format( self.capacitorType )) 

        self.minheight_topPlatecut = self.minWidth_topPlatecut

       # Get the METAL 2/3 layers.
        metal2 = DataBase.getDB().getTechnology().getLayer( 'metal2' )
        metal3 = DataBase.getDB().getTechnology().getLayer( 'metal3' )
       # Get the symbolic pitch.
        rg = AllianceFramework.get().getRoutingGauge()
        self.METAL2Pitch = rg.getHorizontalPitch()
        self.METAL3Pitch = rg.getVerticalPitch()
        self.isVH        = rg.isVH()
        
        foundHor = False
        foundVer = False
        for depth in range(rg.getDepth()):
          rlg = rg.getLayerGauge(depth)
          if rlg.getLayer().getMask() == metal2.getMask(): self.metal2Width = rlg.getWireWidth()
          if rlg.getLayer().getMask() == metal3.getMask(): self.metal3Width = rlg.getWireWidth()

          if rlg.getType() == Constant.PinOnly: continue
          if rlg.getDirection() == Constant.Horizontal and not foundHor:
            self.hpitch = rlg.getPitch()
            foundHor = True
          if rlg.getDirection() == Constant.Vertical and not foundVer:
            self.vpitch = rlg.getPitch()
            foundVer = True

        return


    ## \return capacitor type \c 'MIMCap' or \c 'PIPCap'. 
    #  \remarks \c getCapacitorType() is especially useful when an instance of \c CapacitorUnit class is called in another classes instances to identify the capacitor's type. 

    def getCapacitorType   ( self ) : return self.capacitorType



    ## \retun maximum size of capacitor's top plate. \c getMaximumCapWidth() is called to check if capacitor dimensions are within acceptable technological limits.  
    #  An exception is raised if the entered capacitor type is unknown.
    #  \remarks 1. This function is especially usefull in drawing the layout of a unity capacitor, where it is important to garantee that the capacitor size does not exeed the maximum possible value. It is also useful when drawing a matrix of capacitors to make sure that also the unity capacitor respects the maximal values specified. \remarks 2. The maximum value of the poly2 size in PIP capacitor is not specified. Thus, it is not considered in \c getMaximumCapWidth() 

    def getMaximumCapWidth ( self ) :   

        if   self.capacitorType == 'MIMCap':
            maximumCapWidth = CapacitorUnit.rules.maxWidth_metcap

        elif self.capacitorType == 'PIPCap':
            maximumCapWidth = None #CapacitorUnit.rules.maxWidth_cpoly

        else:
            raise Error( 1, 'getMaximumCapWidth() : Unsupported capacitor type : %s.' % self.capacitorType ) 

        return maximumCapWidth


    ## \return   The minimum size of the capacitor's top plate. An exception is raised if the entered capacitor type is unknown. 
    #  \remarks  This function is especially usefull in drawing the layout of a matrix of capacitors where it is important to ensure that the unity capacitor respects the minimal values specified. \remarks An exception is raised if the entered capacitor type is unknown.

    def getMinimumCapWidth ( self ) :   

        if   self.capacitorType == 'MIMCap':
            minimumCapWidth = CapacitorUnit.rules.minWidth_metcap

        elif self.capacitorType == 'PIPCap':
            minimumCapWidth = CapacitorUnit.rules.minWidth_cpoly

        else:
            raise Error( 1, 'getMinimumCapWidth() : Unsupported capacitor type : %s.' % self.capacitorType ) 

        return minimumCapWidth


    ## Loads the technology file then extracts the adequate layers according to the capacitor type (MIM or PIP). 
    #
    #  \return   a dictionary containing the layer labels as attributes and its values.   
    #  \remarks  An exception is raised if the entered capacitor type is unknown.

    def getLayers( self ):

        technology               =   DataBase.getDB().getTechnology()
        layerDict                =   {}

        if   self.capacitorType  == 'MIMCap':

            layerDict["topPlateLayer"      ] =   technology.getLayer( "metcap" )
            layerDict["bottomPlateLayer"   ] =   technology.getLayer( "metal2" )
            layerDict["topPlateRLayer"     ] =   technology.getLayer( "metal3" )
            layerDict["bottomPlateRLayer"  ] =   technology.getLayer( "metal3" )  
            layerDict["topBottomcutsLayer" ] =   technology.getLayer( "cut2"   )

        elif self.capacitorType == 'PIPCap':

            layerDict["topPlateLayer"      ] =   technology.getLayer( "poly2"  )
            layerDict["bottomPlateLayer"   ] =   technology.getLayer( "poly"   )
            layerDict["topPlateRLayer"     ] =   technology.getLayer( "metal1" )
            layerDict["bottomPlateRLayer"  ] =   technology.getLayer( "metal1" )  
            layerDict["topBottomcutsLayer" ] =   technology.getLayer( "cut0"   )

        else : 
            raise Error( 1, 'getLayers() : Unsupported capacitor type : %s.' % self.capacitorType ) 

        return layerDict


    ## When bonding box mode is activated, the function draws all layout physical layers of the capacitor after checking its dimensions. All functions are excecuted in a new Update Session. In the contrary, only an exact estimation of layout dimensions is given. An error is raised when dimensions reach technological limits for MIM and PIP capacitors or when \c bbMode parameters is other than \c True or \c False.
    #  
    #  \param    ( t , b )    nets of top and bottom plates, respectively
    #  \param      bbMode                            activates bonding box dimensions computing when set to \c True
    #

    def create( self, t, b, bbMode = False, vEnclosure_botPlate_abtBox = None, hEnclosure_botPlate_abtBox = None  ):
   
        UpdateSession.open()           

        abutmentBoxDimensions = None
        self.setRules     ()

        if  self.__isCapacitorUnitOK__( self.capDim ) == True :        

            self.computeDimensions    ( self.capDim, vEnclosure_botPlate_abtBox, hEnclosure_botPlate_abtBox )
            self.drawAbutmentBox      ()
            #self.device.setAbutmentBox( self.abutmentBox )

            if   bbMode == True :
                abutmentBoxDimensions = self.abutmentBoxDict        

            elif bbMode == False :
                layerDict          = self.getLayers()
                self.drawCapacitor( layerDict, t, b )

            else :raise Error(1, 'drawCapacitor(): The bonding box mode parameter, "bbMode" must be either True or False : %s.' %bbMode )

        else : raise Error(1, 'creat(): Impossible to draw the capacitor, dimensions are either too large or too small, "%s".' % self.capDim )

        UpdateSession.close()
   
        return abutmentBoxDimensions



    ## Draws all layout physicial layers of the capacitor. 
    #
    #  \param      layerDict            a dictionary containing a description of the required physical layers according to capacitor type
    #  \param    ( t , b )    nets of top and bottom plates, respectively
    #

    def drawCapacitor( self, layerDict, t, b ):

        self.bottomPlateBox     = self.drawOnePlate( layerDict["bottomPlateLayer"  ] , b                        , self.bottomPlateBoxDict        )   
        self.topPlateBox        = self.drawOnePlate( layerDict["topPlateLayer"     ] , t                        , self.topPlateBoxDict           )  
        self.drawBottomPlateCut                    ( layerDict["topBottomcutsLayer"] , b                                                         )
        self.drawTopPlateCut                       ( layerDict["topBottomcutsLayer"] , t                                                         )
        self.drawRoutingLayers                     ( layerDict["bottomPlateRLayer" ] , layerDict["topPlateRLayer"] , t                    , b )

        return 



    ## Computes needed parameters to draw bottom plate cuts in its exact position, including :
    #
    #  - maximum number of cuts to draw on both sides of bottom plate,
    #  - adjusted enclosure of 
    #  - abcissas of the two bottom cuts on left and right sides of bottom plate,
    #  - ordinate of the same two cuts.
    #
    #  Given parameters described above, it is possible to draw the entire lines of cuts on both sides of bottom plate using \c cutLine function. 
    #

    def computeBottomPlateCuts( self ):

        self.bottomCutLineNumber       =   self.cutMaxNumber( self.bottomPlateBoxDict["height"]
                                                            , self.minheight_topPlatecut
                                                            , self.minSpacingOnBotPlate_cut
                                                            , self.minEnclo_botPlate_botPlateCut
                                                            )
        enclosure_botPlate_botPlateCut = ( self.bottomPlateBoxDict["height"] - (self.bottomCutLineNumber * self.minheight_topPlatecut + (self.bottomCutLineNumber -1) * self.minSpacingOnBotPlate_cut) )/2

        self.cutLeftLineDict ["XMin"]  =   self.topPlateBoxDict   ["XMin"  ] - self.minSpacing_botPlateCut_topPlate - self.minWidth_topPlatecut/2   
        self.cutRightLineDict["XMin"]  =   self.topPlateBoxDict   ["XMin"  ] + self.topPlateBoxDict["width"]        + self.minSpacing_botPlateCut_topPlate + self.minWidth_topPlatecut/2 

        self.cutLeftLineDict ["YMin"]  =   self.bottomPlateBoxDict["YMin"  ] + self.minheight_topPlatecut/2         + enclosure_botPlate_botPlateCut
        self.cutRightLineDict["YMin"]  =   self.cutLeftLineDict   ["YMin"  ]  

        return


    ## Computes needed parameters to draw top plate cuts in its exact position, including :
    #
    #  - maximum number of cuts to draw on both sides of top plate,
    #  - adjusted enclosure of 
    #  - abcissas of the two top cuts on left and right sides of top plate,
    #  - ordinate of the same two cuts.
    #
    #  Given parameters described above, it is possible to draw the entire lines of cuts on both sides of bottom plate using \c cutLine function. 

    def computeTopPlateCuts( self ):

        enclosure_topPlate_topPlateCut             = {}
        self.topCutLineNumber["vertical"]          = self.cutMaxNumber( self.topPlateBoxDict["height"]
                                                                      , self.minheight_topPlatecut
                                                                      , self.minSpacingOnTopPlate_cut
                                                                      , self.minEnclo_topPlate_topPlateCut
                                                                      )
        enclosure_topPlate_topPlateCut["vertical"] = ( self.topPlateBoxDict["height"] - (self.topCutLineNumber["vertical"] *self.minheight_topPlatecut + (self.topCutLineNumber["vertical"] -1)*self.minSpacingOnTopPlate_cut) )/2

        if  self.capacitorType == 'MIMCap':

            self.topCutLineNumber["horizontal"]           = self.cutMaxNumber( self.topPlateBoxDict["width"]
                                                                            , self.minWidth_topPlatecut
                                                                            , self.minSpacingOnTopPlate_cut
                                                                            , self.minEnclo_topPlate_topPlateCut
                                                                            )
            enclosure_topPlate_topPlateCut["horizontal"]  = ( self.topPlateBoxDict["width"] - (self.topCutLineNumber["horizontal"] *self.minheight_topPlatecut + (self.topCutLineNumber["horizontal"] -1)*self.minSpacingOnTopPlate_cut) )/2

            self.cut2MatrixDict           [ "Width"    ]  = (self.topCutLineNumber["horizontal"]- 2)*self.minWidth_topPlatecut  + (self.topCutLineNumber["horizontal"] - 1) * self.minSpacingOnTopPlate_cut 
            [ cut2MatrixAttribute , enclosure_attribute ] = [ ["XMin", "YMin" ] , [ "horizontal" , "vertical" ] ] 
            topPlateBoxCordinates                         = [self.topPlateBoxDict["XMin"], self.topPlateBoxDict["YMin"]]

            for i in range(2):
                topPlateCutTab                                = [ self.minWidth_topPlatecut , self.minheight_topPlatecut ]
                self.cut2MatrixDict[ cut2MatrixAttribute[i] ] =   topPlateBoxCordinates[i]  + topPlateCutTab[i] /2 + enclosure_topPlate_topPlateCut[enclosure_attribute[i]] 

        elif self.capacitorType == 'PIPCap':
            self.topCutLineDict["XMin"] = self.topPlateBoxDict["XMin"] + self.topPlateBoxDict["width"] - self.minEnclo_topPlate_topPlateCut - self.minWidth_topPlatecut/2  
            self.topCutLineDict["YMin"] = self.topPlateBoxDict["YMin"] + self.minheight_topPlatecut/2  + enclosure_topPlate_topPlateCut["vertical"]

        else : 
            raise Error( 1, 'computeTopPlateCuts() : Unsupported capacitor type : %s. Cuts cannot be drawn. ' % self.capacitorType ) 


        return



    def computeRoutingLayersDimensions( self ):

        if self.capacitorType == 'MIMCap' : 

            self.cut2MatrixDict  ["XMax"]         =   self.cut2MatrixDict["XMin"] + self.cut2MatrixDict["Width"]             + self.minWidth_topPlatecut 
            [ topmetalXMin,topmetalXMax ]         = [ self.cut2MatrixDict["XMin"] - self.minEnclo_topPlateRMetal_topPlateCut - self.minWidth_topPlatecut/2
                                                    , self.cut2MatrixDict["XMax"] + self.minEnclo_topPlateRMetal_topPlateCut + self.minWidth_topPlatecut/2
                                                    ]
            [ width_topMetal, width_bottomMetal ] = [ topmetalXMax - topmetalXMin
                                                    , max( self.minWidth_botRMetal, (self.minWidth_botPlatecut + 2*self.minEnclo_botPlateRMetal_botPlateCut) )
                                                    ]
            topMetalXCenter                       = self.abutmentBoxDict["XMin"] + self.abutmentBoxDict["width"]/2 

        elif self.capacitorType == 'PIPCap' :

            topMetalXCenter                       = self.topCutLineDict["XMin"] 
            width_topMetal                        = max( self.minWidth_botRMetal
                                                       , (self.minWidth_topPlatecut + 2*self.minEnclo_topPlateRMetal_topPlateCut)
                                                       , ( self.minWidth_routingTrackcut + 2*self.minEnclo_routingTrackMetal_cut) 
                                                       )
            width_bottomMetal                     = width_topMetal 
      
        else : 
            raise Error( 1, 'computeRoutingLayersDimensions() : Unsupported capacitor type : %s. Routing layers parameters cannot be computed. ' % self.capacitorType ) 

        [ dySourceTop   , dyTargetTop    ]  = [ self.bottomPlateBoxDict["YMin"], self.topPlateBoxDict["YMin"] + self.topPlateBoxDict["height"] ]
        [ dySourceBottom, dyTargetBottom ]  = [ self.bottomPlateBoxDict["YMin"], self.bottomPlateBoxDict["YMin"]+self.bottomPlateBoxDict["height"] ] 

        self.topPlateRLayerDict             = { "YMin" : dySourceTop, "YMax" : dyTargetTop, "XCenter" : topMetalXCenter , "width" : width_topMetal }
        self.bottomPlateRLayerDict          = { "left" : {"XMin": self.cutLeftLineDict["XMin"] - ( self.minEnclo_botPlate_botPlateCut + self.minWidth_botPlatecut/2 ), "XMax": self.cutLeftLineDict["XMin"] + ( self.minEnclo_botPlate_botPlateCut + self.minWidth_botPlatecut/2 )} , "right" : {"XMin": self.cutRightLineDict["XMin"] - ( self.minEnclo_botPlate_botPlateCut + self.minWidth_botPlatecut/2 )}, "YMin": dySourceBottom, "YMax": dyTargetBottom, "width": width_bottomMetal }

        return


    def setBottomPlateAbtBoxEnclosure( self, vEnclosure_botPlate_abtBox, hEnclosure_botPlate_abtBox ):

        if   vEnclosure_botPlate_abtBox == None                            : self.vEnclosure_botPlate_abtBox = self.minSpacing_botPlate
        elif vEnclosure_botPlate_abtBox >= toPhY(self.minSpacing_botPlate) : self.vEnclosure_botPlate_abtBox = toDbU(vEnclosure_botPlate_abtBox)
        else                                                                   : 
            raise Error(1,'setBottomPlateAbtBoxEnclosure() : Bottom plate vertical enclosure in abutment box must be equal or higher than the minimal limit %s um.'
                       %toPhY(self.minSpacing_botPlate))

        if   hEnclosure_botPlate_abtBox == None                            : self.hEnclosure_botPlate_abtBox = self.minSpacing_botPlate
        elif hEnclosure_botPlate_abtBox >= toPhY(self.minSpacing_botPlate) : self.hEnclosure_botPlate_abtBox = toDbU(hEnclosure_botPlate_abtBox)
        else                                                                   : 
            raise Error(1,'setBottomPlateAbtBoxEnclosure() : Bottom plate horizontal enclosure in abutment box must be equal or higher than the minimal limit %s um.' %toPhY(self.minSpacing_botPlate))

        return


        
    def computeAbutmentBoxDimensions( self, capDim, vEnclosure_botPlate_abtBox = None, hEnclosure_botPlate_abtBox = None  ) :

        self.setBottomPlateAbtBoxEnclosure(vEnclosure_botPlate_abtBox, hEnclosure_botPlate_abtBox)
        self.enclosure_botPlate_topPlate =   self.minEnclo_botPlate_botPlateCut + self.minWidth_topPlatecut + self.minSpacing_botPlateCut_topPlate
        abutmentBoxDimensions            =   {}
        abutmentBoxDimensions["width"  ] = 2*self.enclosure_botPlate_topPlate + capDim["width" ] + 2*self.hEnclosure_botPlate_abtBox
        abutmentBoxDimensions["height" ] = 2*self.enclosure_botPlate_topPlate + capDim["height"] + 2*self.vEnclosure_botPlate_abtBox
        #abutmentBoxDimensions["surface"] =   numpy.prod(abutmentBoxDimensions.values())

        return abutmentBoxDimensions


    ##  Draws the abutment box of the capacitor in position \c <(abutmentBoxXMin, abutmentBoxYMin)>. First, the minimum enclosure of the top plate inside the bottom plate is computed. Second, using this parameters as well as the capacitor dimensions, the width and height of the abutment box are computed. The box is finally drawn.

    def drawAbutmentBox( self ):

        self.abutmentBox      = Box(self.abutmentBoxDict["XMin"  ]
                                   ,self.abutmentBoxDict["YMin"  ]
                                   ,self.abutmentBoxDict["width" ] + self.abutmentBoxDict["XMin"]
                                   ,self.abutmentBoxDict["height"] + self.abutmentBoxDict["YMin"]
                                   )
        return


    def computeOnePlateBoxDimensions( self, inputBoxDimensions, vEnclosure, hEnclosure ):

        outputboxDimensions = {}
        outputboxDimensions["XMin"  ] = inputBoxDimensions["XMin"  ] +   hEnclosure 
        outputboxDimensions["YMin"  ] = inputBoxDimensions["YMin"  ] +   vEnclosure 
        outputboxDimensions["width" ] = inputBoxDimensions["width" ] - 2*hEnclosure 
        outputboxDimensions["height"] = inputBoxDimensions["height"] - 2*vEnclosure

        return outputboxDimensions



    def computeDimensions( self, capDim, vEnclosure, hEnclosure ):

        abutmentBoxDimensions = self.computeAbutmentBoxDimensions(capDim, vEnclosure, hEnclosure )
        for key in abutmentBoxDimensions.keys():
            self.abutmentBoxDict[key] = abutmentBoxDimensions[key]

        self.bottomPlateBoxDict = self.computeOnePlateBoxDimensions( self.abutmentBoxDict   , self.vEnclosure_botPlate_abtBox, self.hEnclosure_botPlate_abtBox   )
        self.topPlateBoxDict    = self.computeOnePlateBoxDimensions( self.bottomPlateBoxDict, self.enclosure_botPlate_topPlate, self.enclosure_botPlate_topPlate )
        self.computeBottomPlateCuts()
        self.computeTopPlateCuts()
        self.computeRoutingLayersDimensions()

        return

    ##  Draws the top or bottom plate through inflation of the Box under it. These boxes are the abutment box in the case of the bottom plate and the bottom plate's box in the case of the top plate. This function also creates a a net for the drawn plate and sets it as external.  
    #   \return The drawn box.

    def drawOnePlate( self, layer, net, boxDimensions) : 

        outputPlateBox = Box( boxDimensions["XMin"]
                            , boxDimensions["YMin"]
                            , boxDimensions["XMin"] + boxDimensions["width" ]
                            , boxDimensions["YMin"] + boxDimensions["height"]
                            ) 
        platePad = Pad.create( net, layer, outputPlateBox ) 

        return outputPlateBox


    ## Draws the required cuts to connect the bottom plate. First, the maximal possible number of cuts that can be drawn is computed. Second, using the computed number, the enclosure of this cuts in the bottom plate's layer is adjusted while the minimal enclosure is respected. Third, the relative positions of the cuts on both sides of the plate are computed. Finally, two vertical lines of cuts are drawns.
    #  \remark The relative positions describe the cordinates of the first bottom cut in every line of cuts. Then, knowing the spacing and width specifications of these cuts the rest of the line is easilly constructed.

    def drawBottomPlateCut( self, layer, b ):
      
        self.cutLine( b, layer
                    , self.cutLeftLineDict ["XMin"]
                    , self.cutLeftLineDict ["YMin"]
                    , self.minWidth_botPlatecut
                    , self.minheight_topPlatecut
                    , self.minSpacingOnBotPlate_cut
                    , self.bottomCutLineNumber
                    , 'vertical' 
                    )
        self.cutLine( b, layer
                    , self.cutRightLineDict["XMin"]
                    , self.cutRightLineDict["YMin"]
                    , self.minWidth_botPlatecut
                    , self.minheight_topPlatecut
                    , self.minSpacingOnBotPlate_cut
                    , self.bottomCutLineNumber , 'vertical'
                    )
        
        return


    ## Draws the top plate's cuts after computing the maximal number of cuts that can be placed and its exact enclosure in the top plate.  

    def drawTopPlateCut( self, layer, t ):

        if  self.capacitorType == 'MIMCap':
            self.cutMatrix(t,layer
                          ,self.cut2MatrixDict["XMin"]
                          ,self.cut2MatrixDict["YMin"]
                          ,self.minWidth_topPlatecut
                          ,self.minheight_topPlatecut
                          ,self.minSpacingOnTopPlate_cut
                          ,self.topCutLineNumber["horizontal"]
                          ,self.topCutLineNumber["vertical"  ]
                          )
 
        else : self.cutLine(t,layer
                           ,self.topCutLineDict["XMin"]
                           ,self.topCutLineDict["YMin"]
                           ,self.minWidth_topPlatecut
                           ,self.minheight_topPlatecut
                           ,self.minSpacingOnTopPlate_cut
                           ,self.topCutLineNumber["vertical"]
                           ,'vertical'
                           )

        return


    ## Draws the routing layers of both bottom and top plates after computing widths and the exact position of these layers. Also computes positions if rlayers that are crucial for routing. 

    def drawRoutingLayers( self, bottomPlateLayer, topPlateLayer, t, b ):

        Vertical.create ( t, topPlateLayer
                        , self.topPlateRLayerDict["XCenter"]
                        , self.topPlateRLayerDict["width"  ]
                        , self.topPlateRLayerDict["YMin"   ]
                        , self.topPlateRLayerDict["YMax"   ] 
                        ) 

        cutLinesXMins = [ self.cutLeftLineDict["XMin"], self.cutRightLineDict["XMin"] ]
        for i in range(2):
            Vertical.create ( b, bottomPlateLayer
                            , cutLinesXMins[i]
                            , self.bottomPlateRLayerDict["width"]
                            , self.bottomPlateRLayerDict["YMin" ]
                            , self.bottomPlateRLayerDict["YMax" ]
                            )
        return 


    ## Computes the maximal number of cuts to be placed on a layer of width \c width_layer considering specifications such as the spacing between the cuts, its width and its enclosure in the layer.

    def cutMaxNumber( self, width_layer, width_cut, spacing_cut, enclosure_cut ): 

        cutNumber = int( (width_layer - 2*enclosure_cut + spacing_cut) / (width_cut + spacing_cut) )
        if cutNumber > 0 : 
            return cutNumber
        else : raise Error (1,"cutMaxNumber() : Zero number of cuts found. Layer width is too tight." )



    ## Creates a horizontal or vertical line of contacts according to the specified direction.

    def cutLine( self, net, layer, firstCutXCenter, firstCutYCenter, width_cut, height_cut, spacing_cut, cutNumber, direction ):       
 
        for i in range( cutNumber) :
            segment = Contact.create( net, layer, firstCutXCenter + i*(width_cut + spacing_cut), firstCutYCenter, width_cut, height_cut ) if direction == 'horizontal' else Contact.create( net, layer, firstCutXCenter, firstCutYCenter + i*(height_cut + spacing_cut), width_cut, height_cut )

        return segment


    ## Creates a matrix of cuts by vertically stacking horizontal lines of identical cuts.
    #
    #  \param    net                                  net to which the cuts belong
    #  \param    layer                                cuts physical layer
    #  \param    firstCutXCenter                      center's abcissa of the bottom left cut ( that is the first cut to be drawn in the matrix ) 
    #  \param    firstCutYCenter                      center's abcissa of the bottom left cut
    #  \param    (width_cut,height_cut,spacing_cut)   cuts dimenions 
    #  \param    (cutColumnNumber,cutRowNumber)       matrix dimensions
    #
    #  \remarks  The matrix can have any dimensions zero or negative one. 

    def cutMatrix( self, net, layer, firstCutXCenter, firstCutYCenter, width_cut, height_cut, spacing_cut, cutColumnNumber, cutRowNumber ):

        for i in range( cutRowNumber):
            matrix = self.cutLine( net, layer, firstCutXCenter, firstCutYCenter + i*(spacing_cut + width_cut), width_cut, height_cut, spacing_cut, cutColumnNumber, 'horizontal' )

        return matrix



    ## \return the ordinate of the bottom plate's highest end-point ( that is equivalent to \c dySource of the bottom plate's box ) .   
    def getBottomPlateYMax         ( self ) : return self.bottomPlateBoxDict["YMin"] + self.bottomPlateBoxDict["height"]


    ## \return the abcissa of the bottom plate's left line of cuts.   
    def getBottomPlateLeftCutXMin  ( self ) : return self.cutLeftLineDict      ["XMin"   ] 


    ## \return the ordinate of the first ( or bottom) cut in the left line of cuts on the bottom plate.
    def getBottomPlateLeftCutYMin  ( self ) : return self.cutLeftLineDict      ["YMin"   ]


    ## \return the ordinate of the highest cut of the bottom plate's left line of cuts.   
    def getBottomPlateLeftCutYMax  ( self ) : return self.cutLeftLineDict      ["YMin"   ] +  ( self.bottomCutLineNumber - 1 )*( self.minSpacingOnBotPlate_cut + self.minWidth_botPlatecut )


    ## \return the absissa of the bottom plate's right line of cuts.  
    def getBottomPlateRightCutXMin ( self ) : return self.cutRightLineDict     ["XMin"   ]


    ## \return the ordinate of the first ( or bottom) cut in the right line of cuts on the bottom plate.
    def getBottomPlateRightCutYMin ( self ) : return self.cutRightLineDict     ["YMin"   ]


    ## \return the ordinate of the highest ( or top) cut in the right line of cuts on the bottom plate.
    def getBottomPlateRightCutYMax ( self ) : return self.cutRightLineDict     ["YMin"   ] +  ( self.bottomCutLineNumber - 1 )*( self.minSpacingOnBotPlate_cut + self.minWidth_botPlatecut )


    ## \return the center's ordinate of the bottom plate's left cut (the cut that is the first one in the line).
    def getBotPlateLeftRLayerXMax      ( self ) : return self.bottomPlateRLayerDict["left"   ]["XMax" ]


    ## \return the position of the bottom plate's right cuts on the horitontal axis (also applicable to left cuts).
    def getBottomPlateRightCutYCenter  (self ) : return (self.getBottomPlateRightCutYMax() - self.getBottomPlateRightCutYMin())/2 


     ## \return the position of the bottom plate's right cuts on the horitontal axis.
    def getBotPlateRightRLayerXMin    ( self ) : return self.bottomPlateRLayerDict ["right"  ]["XMin" ]
 

  ## \return the position of the bottom plate's left cuts on the horitontal axis.
    def getBotPlateLeftRLayerXMin    ( self ) : return self.bottomPlateRLayerDict ["left"  ]["XMin" ]
        

    ## \return the position of bottom plate's left cuts on the horitontal axis.
    def getBotPlateRLayerYMin         ( self ) : return self.bottomPlateRLayerDict ["YMin"   ]


    ## \return the position of bottom plate's left cuts on the horitontal axis.
    def getBotPlateRLayerYMax         ( self ) : return self.bottomPlateRLayerDict ["YMax"   ]


    ## \return the position of bottom plate's left cuts on the horitontal axis.
    def getBotPlateRLayerWidth        ( self ) : return self.bottomPlateRLayerDict ["width"  ]


    ## \return the position of bottom plate's left cuts on the horitontal axis.
    def getBotPlateRightRLayerXCenter ( self ) : return self.cutRightLineDict      ["XMin"   ]


    ## \return the position of bottom plate's left cuts on the horitontal axis.
    def getBotPlateLeftRLayerXCenter  ( self ) : return self.cutLeftLineDict       ["XMin"   ]


    ## \return the ordinate of the bottom end points of the top plate routing layer.
    def getTopPlateRLayerYMin         ( self ) : return  self.topPlateRLayerDict   ["YMin"   ]


    ## \return the ordinate of the higher end points of the top plate routing layer.
    def getTopPlateRLayerYMax         ( self ) : return  self.topPlateRLayerDict   ["YMax"   ]


    ## \return the width of top plate's routing layer.
    def getTopPlateRLayerWidth        ( self ) : return  self.topPlateRLayerDict   ["width"  ]


    ## \return the center's abcissa of the bottom plate routing layer.
    def getTopPlateRLayerXCenter      ( self ) : return  self.topPlateRLayerDict   ["XCenter"]


    ## \return the origin (bottom-left end point) abcissa of the top plate routing layers.
    def getTopPlateRLayerXMin         ( self ) : return  self.topPlateRLayerDict   ["XCenter"] - self.topPlateRLayerDict   ["width"]/2


    ## \return the abscissa of the bottom-right end-point of the top plate routing layer.
    def getTopPlateRLayerXMax   ( self ) : return  self.topPlateRLayerDict         ["XCenter"] + self.topPlateRLayerDict   ["width"]/2

   


def scriptMain( **kw ):

    editor = None
    if kw.has_key('editor') and kw['editor']:
        editor = kw['editor']

    UpdateSession.open()
    device = AllianceFramework.get().createCell( 'capacitor' )
    device.setTerminal( True )

    bottomPlate_net = Net.create( device, 'b' )
    bottomPlate_net.setExternal( True )  
    b = device.getNet("b")
    doBreak( 1, 'Done building bottomPlate')

    topPlate_net    = Net.create( device, 't' )
    topPlate_net.setExternal( True )  
    t = device.getNet("t")
    doBreak( 1, 'Done building t')

    if editor:
        UpdateSession.close(        )
        editor.setCell     ( device ) 
        editor.fit         (        )            
        UpdateSession.open (        )     
                      
    capacitor = CapacitorUnit   ( device, 'MIMCap', [0,0], capacitance = 129.56) # square: capacitance : 129.56 , capDim = {"width" : 39650L , "height" : 39650L }                                                                                       # rectangular  capDim = {"width" : 78919L , "height" : 118378L } 
    abutmentBoxDim  = capacitor.create( b, t, bbMode = True, vEnclosure_botPlate_abtBox = 1.5, hEnclosure_botPlate_abtBox = 0.9)          
   #print(abutmentBoxDim)
                      
    AllianceFramework.get().saveCell( device, Catalog.State.Views )

    return True
            
                            
                             
                              
                              
                             
                              
                              
                              
                             
                              
                              
                              
                              
                              
                             
                              
                              
                              
                              
                              
