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
	TextLabel(,"======== Optimise PID Gain ========")
	Long  ClassAsArray
	String    UnitNameTag@{Tag}   
	Integer   PID_Number*<<1>>
	String    PID_Name@{Tag} ;full name of the PID controller
	CheckBox  Optimiser_On*<<1>>
	CheckBox  SetOutput_MinMax*<<0>>
	Options InitGain*<<1>>{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>>
	;full PID tags
	String GainTag@@{Tag}
	String SPTag@@{Tag}
	String MeasTag@@{Tag}
	String RelErrorTag@@{Tag}
	String OutMinTag@@{Tag}
	String OutMaxTag@@{Tag}
	Real OldDiff	;holds the previous error
	Real Diff		;holds the current error
	long classtag_length
	;iteration counters for determining when to adjust
	Integer ItersSinceLastDecrease
	Integer ItersSinceLastIncrease
	;internal flags
	Bit IsFirstIteration
    Bit SetOutputState
Sub Initialise()
	;Check if the Class is defined as an array, if so, need to remove [index] from the classtag()
	ClassAsArray = StrStr(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
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 the OptimiseGain class to PGM file.

Sub CheckVersion()
    Bit BuildOK
    Const long VersionNumber = 32346
    BuildOK = PM.Version(0)>=9 AND PM.Version(1)>=3 AND PM.Version(2)>=139 AND PM.Version(3)>=VersionNumber
    If NOT BuildOK
        ;use the next line for latest versions of SysCAD, Build 139.32346 or later.
        StopSolver("Incorrect version of SysCAD, needs SysCAD9.3 Build139.", IntToStr(VersionNumber)," or later.")
        ;OR use the next two lines for older versions of SysCAD, Build 139.32346 or earlier.
        ;LogError("Incorrect version of SysCAD, needs SysCAD9.3 Build139.", IntToStr(VersionNumber)," or later.")
        ;StopSimulation = true  

;define the class instances 
;We can use the PID unit Tag as the Class Tag, use the 2nd line (array) if multiple control blocks are from the same PID unit.  
Class_OptimiseGain NonOxidising_Leach_Control
;Class_OptimiseGain NonOxidising_Leach_Control[2]

;initialise the class tags (user will need to specify the controller tag in the access window)
Sub PreStart()
   ;initialise the optimise sub routine.  Use the 2nd line if class is defined as array.
   ForEachClass(Class_OptimiseGain, Initialise())
   ;ForEachClass(NonOxidising_Leach_Control, Initialise())

;execute the optimise sub routine. Use the 2nd line if class is defined as array.
ForEachClass(Class_OptimiseGain, Optimise())
;ForEachClass(NonOxidising_Leach_Control, 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. We can try and adjust the "NonOxidising_Leach_Control" - PID block 2.

  1. In the general controller "xxxx.PID_Number" field, type in 2 (to control the PID block 2).
  2. Let's first untick the "xxx.Optimiser_On" box to switch it off.
  3. 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 1000+ 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.
  4. Now go to the PGM and tick the "xxx.Optimiser_On" box.
  5. Repeat the PID setpoint change to see if the solve speed improves.
    • With the optimiser on, it now takes 221 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.