PGM Example - PGM Class to assist with solving sensitive PID control loops

From SysCAD Documentation
Jump to navigation Jump to search

Navigation: PGMs ➔ Example PGM Files ➔ PGM Example - PGM Class to assist with solving sensitive PID control loops

Example User Defined Classes
Set Species Splits Agitator
Aq Feed Conc
Bayer Liquor
Element Bal
Optimise Controllers
for sensitive PIDs
Relaxed Cross Page Connector

Related Links: Defining a Class

;This class is used to optimise the value of the gain for a PID controller
DropList Options{"Maximum Gain", "Minimum Gain", "Geometric Mean"}
Class Class_OptimiseGain
    ClassGridColumnWidth 20 ;if you want multiple optimisers displayed as a grid, set this value so that the column displays are wide enough, search for ClassGrid for more details
    TextLabel ,"    ======== Optimise PID Gain ========" 
    Integer   ClassAsArray
    String    UnitNameTag@{Tag}   
    Integer   PID_Number*<<1>>
    String    PID_Name@{Tag} ;full name of the PID controller
	String    PID_Description@		;description given to the controller by the user
	Memo      PID_Description2@
    CheckBox  Optimiser_On*<<1>>
    CheckBox  SetOutput_MinMax*<<0>>
    Options   InitGain*<<2>>{Comment("Initial Gain (if RelError > 0.1%)")}
    Real      MaxGain*<<0.05>><1e-12,>	    ;the maximum gain that will be used
    Real      MinGain*<<0.0001>><1e-12,>	;the minimum gain that will be used (also used as the initial value)
    Real      CurGain@ 					    ;current gain
    Real      MinPB@@{Comment("1/MaxGain")}, MaxPB@@{Comment("1/MinGain")}, CurPB@@{Comment("1/CurGain")}
    Integer   ItersBetweenGainDecrease*<<5>>	; iterations between gain decrease
    Integer   ItersBetweenGainIncrease*<<5>>	; iterations between gain increase
    Real      AdjustmentFactor*<<2>><1.1,10>	; adjustment factor for division or multiplication
    Real      OutputMin**, OutputMax**<<10>>
    String    GainTag@@{Tag}, SPTag@@{Tag}, MeasTag@@{Tag}, RelErrorTag@@{Tag}, OutMinTag@@{Tag}, OutMaxTag@@{Tag}
    Real      OldDiff	 ;holds the previous error
    Real      Diff       ;holds the current error
    Integer   classtag_length, ItersSinceLastDecrease, ItersSinceLastIncrease
    Bit       IsFirstIteration, SetOutputState
	Sub Initialise()
		;Check if the Class is defined as an array, if so, need to remove [index] from the ClassTag()
		ClassAsArray = StrFind(ClassTag(), "[" )
		if ClassAsArray == -1
			UnitNameTag = ClassTag() 
			classtag_length = StrLen(ClassTag())
			UnitNameTag = Left(ClassTag(), ClassAsArray)
		;calculate and store the PID tags
		PID_Name = Concatenate(UnitNameTag,".Cfg.[", InttoStr(PID_Number),"]")
		GainTag = Concatenate(PID_Name,".Gain")
		SPTag = Concatenate(PID_Name,".SptUsed")
		MeasTag = Concatenate(PID_Name,".Meas")
		RelErrorTag = Concatenate(PID_Name,".RelError (%)")  
		OutMinTag = Concatenate(PID_Name,".OutMin")
		OutMaxTag = Concatenate(PID_Name,".OutMax")
		;initialise key values
		ItersSinceLastDecrease = 0
		ItersSinceLastIncrease = 0
		SetConcealedState(OutputMin, (SetOutput_MinMax==False))
		SetConcealedState(OutputMax, (SetOutput_MinMax==False))
		SetOutputstate = SetOutput_MinMax
	    IsFirstIteration = True
	    PID_Description = [Concatenate(UnitNameTag,".Cfg.[",InttoStr(PID_Number),"].Name")]
	    PID_Description2 = [Concatenate(UnitNameTag,".Cfg.[",InttoStr(PID_Number),"].Description")]
	Sub Optimise()
		If (Optimiser_On)
			If (IsFirstIteration)
				If (Abs([RelErrorTag]) > 0.1) 	;if not currently converged
					If InitGain == 0 			;start at max gain
						[GainTag] = MaxGain
					ElseIf InitGain == 1 		;start at min gain
						[GainTag] = MinGain
					ElseIf InitGain == 2 		;start at geometric mean
						[GainTag] = Sqrt(MinGain * MaxGain) 
				CurGain = [GainTag]
				OldDiff = [RelErrorTag]
				IsFirstIteration = False ;reset flag
			;grab current error
			Diff = [RelErrorTag]
			If (Diff * OldDiff < 0) ;error has switched sign (oscillating around SP) - decrease gain
				If (ItersSinceLastDecrease > ItersBetweenGainDecrease)
					CurGain = Max(CurGain/AdjustmentFactor, MinGain)
					[GainTag] = CurGain
					ItersSinceLastDecrease = 0
			Else ;error has the same sign (approaching SP) - increase gain
				If (ItersSinceLastIncrease > ItersBetweenGainIncrease)
					CurGain = Min(CurGain*AdjustmentFactor, MaxGain)
					[GainTag] = CurGain
					ItersSinceLastIncrease = 0
			;update counter and previous error
			ItersSinceLastDecrease = ItersSinceLastDecrease + 1
			ItersSinceLastIncrease = ItersSinceLastIncrease + 1
			OldDiff = Diff
		;update concealed state of output min/max if changed
		If SetOutput_MinMax <> SetOutputState
			SetConcealedState(OutputMin, (SetOutput_MinMax==False))
			SetConcealedState(OutputMax, (SetOutput_MinMax==False))
			SetOutputState = SetOutput_MinMax
		;update PID output min/max
		If SetOutput_MinMax
			[OutMinTag] = OutputMin
			[OutMaxTag] = OutputMax
		CurPB = 1/CurGain
		MaxPB = 1/MinGain
		MinPB = 1/MaxGain
;Please make sure there is no dollar sign or end of file marker here for this to be an include file.

Adding Version Check (optional) This will ensure SysCAD is in the correct version to use the latest PGM syntax / functions.

Const long VersionNumber = 32530

Sub CheckVersion()
    Bit BuildOK
    BuildOK = PM.Version(0)>=9 AND PM.Version(1)>=3 AND PM.Version(2)>=139 AND PM.Version(3)>=VersionNumber
    If NOT BuildOK
        StopSolver("Incorrect version of SysCAD, needs SysCAD9.3 Build139.", IntToStr(VersionNumber)," or later.")
'''Adding the OptimiseGain class to PGM file.'''  

;Main Program 
PageLabel "OptimisePIDs" 
;Define the class instances
;For PID Controller with a single control block requiring optimisation, simply define the class instance using the unit tag name.
  Class_OptimiseGain Autoclave2_Steam
;For PID Controller with multiple control blocks requiring optimisation, define the class instance using the unit tag name in an array.  
  Class_OptimiseGain NonOxidising_Leach_Control[2]
;Define class list for use in class macros or class functions
  ClassList PIDs{Autoclave2_Steam, NonOxidising_Leach_Control}
;Display results in Grid format
  ClassGrid PIDs
;initialise the class tags
 Sub PreStart()
   ForEachSub(PIDs, Initialise())

;execute the optimise sub routine. 
   ForEachSub(PIDs, Optimise())

$ ; --- end of file ---

Setting up the Access Window:

For example, user can add the above PGM class and PGM file to the Nickel Copper Project.

  1. We have added three PID blocks in this PGM file
    • Add control unit with single control block using "Unit TagName". (e.g. Autoclave2_Steam)
    • Add control unit with multiple control blocks using "Unit TagName array". (e.g. NonOxidising_Leach_Control[2])
    • We will use this last controller for acid as an example on how to set up the values.
  2. In the general controller "xxxx.PID_Number" field, make sure you put in the correct PID block number, in our example, the last entry should have "2" entered to control PID block 2.
  3. Let's first untick the "xxx.Optimiser_On" box to switch it off.
  4. Go to the PID block and change discharge acid setpoint from 5 g/L to 0.01 g/L and solve.
    • We can see the Gain is currently set at 0.05, and it took 700+ iteration to solve to the new setpoint.
    • Change the Setpoint back to 5 and solve. It solved a lot faster as the loop is set up for a setpoint close to this value.
    • Now go to the PGM and tick the "xxx.Optimiser_On" box.
    • Repeat the PID setpoint change to see if the solve speed improves.
    • With the optimiser on, it now takes ~150 iterations to solve to the new setpoint. The Gain is now set to 0.039.


  • User may need to adjust the Min and Max Gain values if the gain tuning is not working.
  • Adjusting the frequency and adjustment factor would also help.
  • If the PID controller output min and max values need to be ranged, user can tick the SetOutput_MinMax box and set some ranges.
  • If the PID controller needs to start very slow (such as any pH controller), then select option "minimum gain" for the "initGain".