<commit_before># Twisted Imports
from twisted.python import log

# Package Imports
from ..constants import State
from ..util import now

# Sibling Imports
import util
import error


class Bind (util.Looping, util.Dependent):
	"""
	Connects a variable to an expression.
	
	Each iteration, the value of expr is evaluated. 
	If process is defined, then the result is passed through this function.
	(If expr is None, then the return value of process is used).
	
	variable is updated with this new value.
	
	Process must be a function that accepts one parameter, 
	usually a Variable or Expression.

	Set max and min to limit the value of variable.
	"""

	@property
	def interval (self):
		return self._interval

	# NB altering interval doesn't work when running.
	# Maybe make an EditableInterval mixin that restarts the LoopingCall
	# if it is running.
	@interval.setter
	def interval (self, value):
		self._interval = value

	max = None
	min = None

	class NoUpdate (Exception):
		"""
		Throw this exception from process function to prevent variable
		from being changed.
		"""

		pass

	def __init__ (self, variable, expr = None, process = None):
		util.Dependent.__init__(self)
		util.Looping.__init__(self, interval = 0.5)

		self.variable = variable
		self.expr = expr
		self.process = process

	def _iterate (self):
		# Use self.process if set
		if callable(self.process):
			try:
				new_val = self.process(self.expr)
			except NoUpdate:
				return
		else:
			new_val = self.expr.value

		# Enforce bounds
		if self.max is not None:
			new_val = min(new_val, self.max)
		if self.min is not None:
			new_val = max(new_val, self.min)

		# Temporary optimisation - eventually add update triggers to variables.
		if self.variable.value != new_val:
			# Return a value so that self._calls gets incremented,
			# swallow any errors to avoid terminating the loop.
			return self.variable.set(new_val).addErrback(log.err)



class PID (util.Looping, util.Dependent):
	"""
	PID controller.
	
	Each iteration, response is updated with a new value based on
	the error variable.
	
	Alter the proportional, integral and differential parameters
	to alter the nature of the response.

	Set max and min to limit the response value.
	"""

	@property
	def interval (self):
		return self._interval

	@interval.setter
	def interval (self, value):
		self._interval = value

	proportional = 25
	integral = 0.25
	differential = 500

	max = None
	min = None

	def __init__ (self, response, error):
		util.Dependent.__init__(self)
		util.Looping.__init__(self, interval = 0.5)

		self.error = error
		self.response = response
		self._prev_error = None

	def _iterate (self):
		current = float(self.response)
		interval = float(self.interval)

		e = float(self.error)
		e_prev, self._prev_error = self._prev_error, e

		if e_prev is None:
			return

		# trapezium rule
		integral_term = \
			(min(e, e_prev) * interval) + \
			(0.5 * (e + e_prev) * interval)

		# difference rule
		differential_term = (e - e_prev) / interval

		# PID control
		change = \
			(self.proportional * e) + \
			(self.integral * integral_term) + \
			(self.differential * differential_term)

		new = current + change

		# Enforce bounds
		if self.max is not None:
			new = min(new, self.max)
		if self.min is not None:
			new = max(new, self.min)

		self.response.set(new)


class StateMonitor (util.Looping, util.Dependent):
	"""
	Monitors a set of expressions.

	Add an expression to the set of tests with:
		monitor.add(expr)

	If any of the monitors becomes False, monitor.trigger_step is run.
	When the monitors become True, monitor.trigger_step is cancelled and
	monitor.reset_step is run. (If the monitor is triggered again,
	reset_step is cancelled before trigger_step is run).
	
	Parameters:
		auto_reset: If False, then reset_trigger() must be called
		            before the monitor can be triggered again.
		cancel_on_trigger: If False, then reset_step will not be<commit_msg>Update to PID controller<commit_after># Twisted Imports
from twisted.python import log

# Package Imports
from ..constants import State
from ..util import now

# Sibling Imports
import util
import error


class Bind (util.Looping, util.Dependent):
	"""
	Connects a variable to an expression.
	
	Each iteration, the value of expr is evaluated. 
	If process is defined, then the result is passed through this function.
	(If expr is None, then the return value of process is used).
	
	variable is updated with this new value.
	
	Process must be a function that accepts one parameter, 
	usually a Variable or Expression.

	Set max and min to limit the value of variable.
	"""

	@property
	def interval (self):
		return self._interval

	# NB altering interval doesn't work when running.
	# Maybe make an EditableInterval mixin that restarts the LoopingCall
	# if it is running.
	@interval.setter
	def interval (self, value):
		self._interval = value

	max = None
	min = None

	class NoUpdate (Exception):
		"""
		Throw this exception from process function to prevent variable
		from being changed.
		"""

		pass

	def __init__ (self, variable, expr = None, process = None):
		util.Dependent.__init__(self)
		util.Looping.__init__(self, interval = 0.5)

		self.variable = variable
		self.expr = expr
		self.process = process

	def _iterate (self):
		# Use self.process if set
		if callable(self.process):
			try:
				new_val = self.process(self.expr)
			except NoUpdate:
				return
		else:
			new_val = self.expr.value

		# Enforce bounds
		if self.max is not None:
			new_val = min(new_val, self.max)
		if self.min is not None:
			new_val = max(new_val, self.min)

		# Temporary optimisation - eventually add update triggers to variables.
		if self.variable.value != new_val:
			# Return a value so that self._calls gets incremented,
			# swallow any errors to avoid terminating the loop.
			return self.variable.set(new_val).addErrback(log.err)



class PID (util.Looping, util.Dependent):
	"""
	PID controller.
	
	Each iteration, response is updated with a new value based on
	the error variable.
	
	Alter the proportional, integral and differential parameters
	to alter the nature of the response.

	Set max and min to limit the response value.
	"""

	@property
	def interval (self):
		return self._interval

	@interval.setter
	def interval (self, value):
		self._interval = value

	proportional = 0.5
	integral = 0.05
	differential = 0

	max = None
	min = None

	max_integral = 500
	min_integral = -500

	def __init__ (self, response, error, proportional = None, integral = None, differential = None, max = None, min = None, interval = 0.5):
		util.Dependent.__init__(self)
		util.Looping.__init__(self, interval)

		self.error = error
		self.response = response

		if proportional is not None:
			self.proportional = proportional
		if integral is not None:
			self.integral = integral
		if differential is not None:
			self.differential = differential

		self.max = max
		self.min = min

		self._prev_error = None
		self._integral = 0 # Accumulated integral

	def reset_integral (self):
		self._integral = 0

	def _integral_bound (self, integral):
		if self.max_integral is not None:
			integral = min(integral, self.max_integral)
		if self.min_integral is not None:
			integral = max(integral, self.min_integral)

		return integral

	def _output_bound (self, new):
		if self.max is not None:
			new = min(new, self.max)
		if self.min is not None:
			new = max(new, self.min)

		return new

	def _iterate (self):
		current = float(self.response)
		interval = float(self.interval)

		e = float(self.error)
		e_prev, self._prev_error = self._prev_error, e

		if e_prev is None:
			return

		# Trapezium rule
		integral_term = self._integral + \
			(min(e, e_prev) * interval) + \
			(0.5 * (e + e_prev) * interval)

		integral_term = self._integral_bound(integral_term)
		self._integral = integral_term

		# Difference rule
		differential_term = (e - e_prev) / interval
		self._differential = differential_term

		# Compute PID
		change = \
			(self.proportional * e) + \
			(self.integral * integral_term) + \
			(self.differential * differential_term)

		new = current + change

		# Enforce bounds
		new = self._output_bound(new)

		self.response.set(new)


class StateMonitor (util.Looping, util.Dependent):
	"""
	Monitors a set of expressions.

	Add an expression to the set of tests with:
		monitor.add(expr)

	If any of the monitors becomes False, monitor.trigger_step is run.
	When the monitors become True, monitor.trigger_step is cancelled and
	monitor.reset_step is run. (If the monitor is triggered again,
	reset_step is cancelled before trigger_step is run).
	
	Parameters:
		auto_reset: If False, then reset_trigger() must be called
		            before the monitor can be triggered again.
		cancel_on_trigger: If False, then reset_step will not be
{'fraction_non_alphanumeric': 0.08224468931046235, 'fraction_numerical': 0.003862319663750994, 'mean_word_length': 3.464503042596349, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5420275', 'n_tokens_mistral': 3047, 'n_tokens_neox': 2664, 'n_words': 1000}