pidcontroller

package
v1.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 27, 2026 License: MIT Imports: 3 Imported by: 0

README

PID Controller Package

License Go Version Coverage

This package provides a standard implementation of a Proportional-Integral-Derivative (PID) controller. It is designed to generate a smooth sequence of values from a starting point to a target, making it useful for simulations, animations, and control systems where gradual adjustment is required.


Table of Contents


Overview

The pidcontroller package offers a flexible and easy-to-use PID controller that calculates an error value as the difference between a desired setpoint and a measured process variable. It then applies a correction based on proportional, integral, and derivative terms.

Design Philosophy

The design is centered around simplicity and efficiency:

  1. Interface-Based: The core logic is exposed through a simple PID interface, promoting loose coupling and testability.
  2. Context-Aware: Asynchronous operations are supported via context.Context, allowing for cancellation and timeouts.
  3. Performance-Tuned: The implementation has been optimized to reduce memory allocations and CPU overhead in tight loops.
Key Features
  • Standard PID Algorithm: Implements the classic Proportional-Integral-Derivative control logic.
  • Context Cancellation: Supports context.Context for managing the lifecycle of value generation.
  • Configurable Gains: Allows tuning of Kp, Ki, and Kd coefficients to customize controller behavior.
  • High Performance: Optimized for low-overhead execution in performance-critical applications.
Key Benefits
  • Smooth Value Transitions: Ideal for scenarios requiring gradual adjustments, such as animations or simulated physical processes.
  • Predictable Control: Provides a standard, well-understood mechanism for control loop feedback.
  • Robust and Tested: Comes with a comprehensive test suite, including benchmarks and race detection.

Architecture

Package Structure
pidcontroller/
├── doc.go              # Package documentation and concepts.
├── interface.go        # Defines the main PID interface and constructor.
├── model.go            # Contains the concrete implementation of the PID controller.
├── helper.go           # Utility functions for type conversions.
├── pid_test.go         # BDD tests for the PID controller.
├── helper_test.go      # Tests for helper functions.
└── examples_test.go    # Usage examples.
Dataflow

The controller operates on a simple feedback loop:

  Setpoint (max)
       |
       v
+-----------+     Error (e)      +-----------+
|           | -----------------> |           |
| Comparator|                    |    PID    |
| (in calc) | <----------------  | Algorithm |
+-----------+                    |           |
       ^                         +-----------+
       |                               | Control Output (step)
       |                               v
Process Variable (min)           +-----------+
                                 |   Loop    |
                                 +-----------+
  1. The calc method computes the error between the max (setpoint) and min (current value).
  2. The PID algorithm calculates the step (control output) based on Kp, Ki, and Kd gains.
  3. The main loop in RangeCtx adds the step to min, driving it toward max.

Performance

The controller is designed for efficiency. Benchmarks show that it performs well across a range of scenarios, with minimal allocations. The context check is amortized to reduce its impact in tight loops.

CPU and Memory Usage
  • CPU: The primary CPU load comes from the calc function, which performs the core PID arithmetic. In benchmarks, RangeCtx and its callees account for the majority of CPU time, which is expected. The amortized context check (check%100) successfully reduces the overhead of ctx.Err() in tight loops.
  • Memory: Memory allocation is dominated by the creation and growth of the result slice in RangeCtx. The initial capacity is set to 100 elements to minimize reallocations for common use cases. Benchmarks show that BenchmarkPIDRangeCtx makes only one allocation per operation.
Benchmark Time/Op Allocs/Op
BenchmarkPIDRange ~1228 ns/op 5
BenchmarkPIDRangeCtx ~373 ns/op 1
BenchmarkPIDRangeSmallSteps ~46458 ns/op 11
BenchmarkPIDRangeLargeRange ~1759 ns/op 5

Results from an Intel Core i7-4700HQ CPU.


Use Cases

1. Smooth Animation or Easing

Simulate a smooth transition for UI elements or game objects. By tuning the PID gains, you can achieve effects like easing, overshoot, and damping.

// Simulate an object moving from position 0 to 100 with some damping.
pid := pidcontroller.New(0.2, 0.1, 0.5)
positions := pid.Range(0, 100)
for _, pos := range positions {
    fmt.Printf("Object at position: %.2f\n", pos)
    // Update UI or game object position here.
}
2. Resource Throttling

Control the rate of a process, such as adjusting the number of active workers in a pool based on CPU or memory load.

// Target 80% CPU usage. Current usage is 50%.
// The PID controller can determine how many workers to add.
pid := pidcontroller.New(0.5, 0.2, 0.1)
adjustments := pid.Range(50, 80) // From 50% load to 80% target
fmt.Printf("CPU adjustment steps: %v\n", adjustments)
3. Dynamic Backoff/Retry Delay

Generate a sequence of increasing delays for retrying a failed operation, such as a network request. This provides a smoother alternative to exponential backoff.

// Generate backoff delays from 5 seconds to 1 minute.
pid := pidcontroller.New(0.1, 0.05, 0.05)
minDelay := float64(5 * time.Second)
maxDelay := float64(1 * time.Minute)

delays := pid.Range(minDelay, maxDelay)

for i, d := range delays {
    fmt.Printf("Attempt %d: wait for %s\n", i+1, time.Duration(d).Round(time.Second))
}

Quick Start

Installation
go get github.com/nabbar/golib/pidcontroller
Basic Implementation

This example demonstrates creating a PID controller and using it to generate a sequence of values from a start point to an end point.

package main

import (
	"fmt"
	"github.com/nabbar/golib/pidcontroller"
)

func main() {
	// Initialize a PID controller with proportional, integral, and derivative gains.
	pid := pidcontroller.New(0.5, 0.1, 0.2)

	// Generate a sequence of values from 0 to 10.
	// The Range function uses a default timeout to prevent infinite loops.
	values := pid.Range(0, 10)

	// Print the generated steps.
	for i, v := range values {
		fmt.Printf("Step %d: %.2f\n", i+1, v)
	}
}

Best Practices

✅ DO
  • Tune Gains Carefully: Start with Kp and gradually introduce Ki and Kd. A high Ki can lead to overshoot, while a high Kd can cause instability.
  • Use RangeCtx for Long-Running Tasks: Always provide a context with a timeout for any process that is not guaranteed to complete quickly.
  • Pre-allocate Slices if Possible: Although the internal implementation pre-allocates, for extreme performance needs, consider if you can estimate the number of steps.
❌ DON'T
  • Use High Gains Without Testing: Large Kp, Ki, or Kd values can lead to instability and wild oscillations.
  • Forget Timeouts: Never call RangeCtx with context.Background() unless you are certain the loop will terminate. The Range method provides a safe default.
  • Assume State is Preserved Across Calls: Each call to Range or RangeCtx uses a fresh internal state for the PID calculation (integral and prevError are reset).

API Reference

pidcontroller.PID Interface
Function Parameters Returns Description
Range min, max float64 []float64 Generates a sequence from min to max with a default timeout.
RangeCtx ctx context.Context, min, max float64 []float64 Generates a sequence, respecting the provided context for cancellation.
pidcontroller.New
Function Parameters Returns Description
New rateProportional, rateIntegral, rateDerivative float64 PID Creates a new PID controller with the specified gains.

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Code Quality

    • Follow Go best practices and idioms
    • Maintain or improve code coverage (target: >80%)
    • Pass all tests including race detector
    • Use gofmt, golangci-lint and gosec
  2. AI Usage Policy

    • AI must NEVER be used to generate package code or core functionality
    • AI assistance is limited to:
      • Testing (writing and improving tests)
      • Debugging (troubleshooting and bug resolution)
      • Documentation (comments, README, TESTING.md)
    • All AI-assisted work must be reviewed and validated by humans
  3. Testing

    • Add tests for new features
    • Use Ginkgo v2 / Gomega for test framework
    • Ensure zero race conditions
    • Maintain coverage above 80%
  4. Documentation

    • Update GoDoc comments for public APIs
    • Add examples for new features
    • Update README.md and TESTING.md if needed
  5. Pull Request Process

    • Fork the repository
    • Create a feature branch
    • Write clear commit messages
    • Ensure all tests pass
    • Update documentation
    • Submit PR with description of changes

Resources

Package Documentation
  • GoDoc - Official package documentation.
  • TESTING.md - Detailed guide on testing, benchmarks, and quality assurance for this package.
External References

AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2020-2026 Nicolas JUHEL

Documentation

Overview

Package pidcontroller implements a Proportional-Integral-Derivative (PID) controller.

A PID controller is a control loop mechanism employing feedback that is widely used in industrial control systems and a variety of other applications requiring continuously modulated control. A PID controller calculates an error value as the difference between a desired setpoint (SP) and a measured process variable (PV) and applies a correction based on proportional, integral, and derivative terms (denoted P, I, and D respectively).

Concepts

The PID controller algorithm involves three separate constant parameters, and is accordingly sometimes called three-term control: the proportional, the integral and derivative values, denoted P, I, and D. Simply put, these values can be interpreted in terms of time: P depends on the present error, I on the accumulation of past errors, and D is a prediction of future errors, based on current rate of change. The weighted sum of these three actions is used to adjust the process via a control element such as the position of a control valve, a damper, or the power supplied to a heating element.

Data Flow

The following diagram illustrates the data flow within the PID controller:

 Setpoint (SP)
      |
      v
+-----------+     Error (e)     +-----------+
|           | ----------------> |           |
| Comparator|                   |    PID    |
|           | <---------------- | Algorithm |
+-----------+                   |           |
      ^                         +-----------+
      |                               | Control Variable (u)
      |                               v
Process Variable (PV)          +-----------+
                               |  Process  |
                               +-----------+

Mathematical Model

The overall control function u(t) can be expressed mathematically as:

u(t) = Kp * e(t) + Ki * ∫ e(t) dt + Kd * de(t)/dt

Where:

  • Kp is the proportional gain, a tuning parameter.
  • Ki is the integral gain, a tuning parameter.
  • Kd is the derivative gain, a tuning parameter.
  • e(t) = SP - PV(t) is the error (SP is the setpoint, and PV(t) is the process variable).
  • t is the time or instantaneous time (the present).
  • τ is the variable of integration (takes on values from time 0 to the present t).

Usage

This package provides a simple interface `PID` to create and use a PID controller. The primary use case is generating a smooth transition from a starting value to a target value, controlled by the PID parameters to simulate physical constraints or desired behaviors (e.g., easing, overshoot).

Quick Start

To use the PID controller, first create a new instance using the `New` function, providing the P, I, and D coefficients. Then, use the `Range` or `RangeCtx` methods to generate a sequence of values.

Example:

package main

import (
	"fmt"
	"github.com/nabbar/golib/pidcontroller"
)

func main() {
	// Initialize a PID controller with specific gains
	// Kp = 0.5 (Proportional)
	// Ki = 0.1 (Integral)
	// Kd = 0.2 (Derivative)
	pid := pidcontroller.New(0.5, 0.1, 0.2)

	// Generate a range of values from 0 to 100
	// The PID controller will determine the steps based on the gains.
	values := pid.Range(0, 100)

	// Output the generated sequence
	for i, v := range values {
		fmt.Printf("Step %d: %f\n", i, v)
	}
}

Use Cases

1. Smooth Animation: Calculating intermediate frames for an animation where the transition needs to be natural and physics-based. 2. Process Simulation: Simulating a heating process where temperature rises to a setpoint. 3. Rate Limiting: Controlling the rate of resource consumption or request processing.

Example (Duration)

Example_duration demonstrates how to use the PID controller to generate a sequence of durations between a minimum and maximum wait time. This can be useful for implementing backoff strategies.

package main

import (
	"fmt"
	"time"

	"github.com/nabbar/golib/pidcontroller"
)

func main() {
	// Initialize a PID controller suitable for duration calculation.
	// A lower Kp will result in more, smaller steps.
	pid := pidcontroller.New(0.05, 0.01, 0.01)

	// Define the minimum and maximum durations (e.g., 5s to 1m).
	minDuration := 5 * time.Second
	maxDuration := 1 * time.Minute

	// Convert durations to float64 (nanoseconds) for the PID controller.
	start := float64(minDuration)
	end := float64(maxDuration)

	// Generate the sequence of durations.
	steps := pid.Range(start, end)

	fmt.Printf("Generating backoff steps from %s to %s\n", minDuration, maxDuration)
	fmt.Printf("Total steps: %d\n", len(steps))

	if len(steps) > 0 {
		// Convert the first and last step back to time.Duration for display.
		firstStep := time.Duration(steps[0])
		lastStep := time.Duration(steps[len(steps)-1])
		fmt.Printf("First backoff: %s\n", firstStep.Round(time.Second))
		fmt.Printf("Last backoff: %s\n", lastStep.Round(time.Second))
	}

}
Output:
Generating backoff steps from 5s to 1m0s
Total steps: 13
First backoff: 9s
Last backoff: 1m0s

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Float64ToInt64 added in v1.21.0

func Float64ToInt64(i float64) int64

Float64ToInt64 converts a float64 value to an int64 representation. It handles potential overflow/underflow by clamping the value to the range of int64.

func Int64ToFloat64 added in v1.21.0

func Int64ToFloat64(i int64) float64

Int64ToFloat64 converts an int64 value to a float64 representation. This is a simple type conversion helper.

Types

type PID

type PID interface {
	// RangeCtx generates a slice of float64 values representing the progression from a minimum value
	// to a maximum value, utilizing the PID controller to determine the step size at each iteration.
	//
	// The provided context.Context allows for cancellation or timeout of the operation.
	// If the context is cancelled before the target is reached, the function returns the values
	// generated up to that point, appending the target value as the final element to ensure closure.
	//
	// Parameters:
	//   - ctx: The context to manage the lifecycle of the operation (e.g., timeout, cancellation).
	//   - min: The starting value (Process Variable initial state).
	//   - max: The target value (SetPoint).
	//
	// Returns:
	//   A slice of float64 containing the sequence of values generated by the PID controller.
	RangeCtx(ctx context.Context, min, max float64) []float64

	// Range acts as a wrapper around RangeCtx with a default timeout.
	// It generates a sequence of values from min to max.
	//
	// This method enforces a default timeout (e.g., 5 seconds) to prevent infinite loops
	// in case the PID controller fails to converge or the target is unreachable.
	//
	// Parameters:
	//   - min: The starting value.
	//   - max: The target value.
	//
	// Returns:
	//   A slice of float64 containing the sequence of values.
	Range(min, max float64) []float64
}

PID defines the interface for a Proportional-Integral-Derivative controller. It provides methods to generate a sequence of values transitioning from a start point to a target point, controlled by the PID algorithm.

func New

func New(rateProportional, rateIntegral, rateDerivative float64) PID

New creates and initializes a new PID controller instance.

The PID controller is initialized with the specified coefficients for the Proportional, Integral, and Derivative terms. These coefficients dictate the behavior of the controller:

  • rateProportional (Kp): Reacts to the current error.
  • rateIntegral (Ki): Reacts to the accumulation of past errors.
  • rateDerivative (Kd): Reacts to the rate of change of the error.

Parameters:

  • rateProportional: The proportional gain coefficient.
  • rateIntegral: The integral gain coefficient.
  • rateDerivative: The derivative gain coefficient.

Returns:

A new instance of the PID interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL