Module coinor.grumpy.forecasting
Expand source code
from __future__ import division
from __future__ import print_function
from past.utils import old_div
from builtins import object
# BAK_visual.py
#
# Copyright 2009 Google Inc.
# Copyright 2007 University of Pittsburgh.
# Google coding done by Brady Hunsaker.
# U of Pittsburgh coding done by Osman Ozaltin and Brady Hunsaker.
#
# This file is part of BAK (Branch-and-bound Analysis Kit).
#
# The contents of this file are subject to the Common Public License
# 1.0. (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of
# the Common Public License along with STOP.
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# Alternatively, the contents of this file may be used under the
# terms of the GNU General Public License Version 2 or later (the
# "GPL"), in which case the provisions of the GPL are applicable
# instead of those above. If you wish to allow use of your version
# of this file only under the terms of the GPL, and not to allow
# others to use your version of this file under the terms of the
# CPL, indicate your decision by deleting the provisions above and
# replace them with the notice and other provisions required by the
# GPL. If you do not delete the provisions above, a recipient may
# use your version of this file under the terms of either the CPL or
# the GPL.
#
# For developers: Please keep the code style consistent with the Python
# style guide for Google's Summer of Code, except use 4 spaces to indent:
# http://code.google.com/p/soc/wiki/PythonStyleGuide
__author__ = 'Brady Hunsaker, Osman Ozaltin'
__maintainer__ = 'Brady Hunsaker (bhunsaker@google.com)'
"""Double exponential smoothing forecasting in chained sequences.
A single sequence is stored in a DoubleExponentialSmoothingForecaster, which
also provides forecasts for time to completion based on the stored measures,
which should be monotonically decreasing.
Sequences are chained together in a ForecastingChainedSequences object.
Such an object includes scale factors for each sequence. The scale factors
will be applied to the measurements, usually for the purpose of making the
chained sequence monotonically decreasing.
"""
import math
class ProgressMeasurement(object):
"""Key data recording progress. Data members are public.
"""
def __init__(self, time, value, active_node_count, node_count):
self.time = float(time)
self.value = float(value)
self.active_node_count = int(active_node_count)
self.node_count = int(node_count)
class TimeForecast(object):
"""Time-stamped forecast of time remaining.
"""
def __init__(self, time, forecast):
self.time = float(time)
self.forecast = float(forecast)
class DoubleExponentialSmoothingForecaster(object):
"""Uses double exponential smoothing to forecast values.
"""
def __init__(self, scale_factor, first_value, first_time):
self._measures = []
self._forecasts = []
self._b_t = None
self._S_t = None
self._scale_factor = float(scale_factor)
self._first_time = -1
if first_value is None:
self._first_value = 1
else:
self._first_value = float(first_value)
self._alpha = 0.5
self._lambda = 0.5
self._gamma = 0.5
self._counter = 0
def AddMeasure(self, measurement):
self._measures.append(measurement)
self.ComputeForecast()
def ComputeForecast(self):
# Check if the forecast was already computed
if len(self._forecasts) == len(self._measures) - 3:
return
# Must have at least 4 measurements to make a forecast.
if len(self._measures) < 4:
return
# Check that a minimum amount of time has passed
if self._measures[-1].time - self._measures[-2].time < 1:
return
measure_value = self._measures[-1].value * self._scale_factor
previous_value = self._measures[-2].value * self._scale_factor
time = self._measures[-1].time
previous_time = self._measures[-2].time
delta = old_div(float((measure_value - previous_value)), float((time - previous_time)))
if -delta < 0.00001 * measure_value:
delta = 0
# Set initial parameters if necessary otherwise update the estimators
if self._b_t is None:
if self._first_value > 0:
self._b_t = delta
self._S_t = (measure_value * self._alpha +
previous_value * (1 - self._alpha))
else:
self._b_t = self._first_value
self._S_t = (measure_value * self._alpha +
previous_value * (1 - self._alpha))
if self._b_t < -measure_value:
self._b_t = -measure_value
self._S_t = (measure_value * self._alpha +
previous_value * (1 - self._alpha))
updated = False
if delta < 0:
self._b_t = self._gamma * delta + (1 - self._gamma) * self._b_t
self._S_t = (self._alpha * measure_value +
(1 - self._alpha) * self._S_t)
updated = True
print('delta: %f' %delta)
print('self._lambda*self._b_t: %f' %(self._lambda*self._b_t))
if delta < self._lambda * self._b_t:
if not updated:
self._b_t = self._gamma * delta + (1 - self._gamma) * self._b_t
self._S_t = (self._alpha * measure_value +
(1 - self._alpha) * self._S_t)
updated = True
forecast = (time + (old_div(float(-self._S_t), min(delta,self._b_t))))
print('A', time, previous_value, measure_value, end=' ')
print(self._S_t, self._b_t, forecast)
#cent = float(input("STOP"))
elif len(self._forecasts) >= 1:
# The measure didn't change but we have a previous forecast
if self._forecasts[-1].forecast >= time:
forecast = self._forecasts[-1].forecast
else:
forecast = (time +
(old_div(float(self._measures[-1].active_node_count),
self._measures[-2].active_node_count)) *
(self._forecasts[-1].forecast -
self._forecasts[-1].time))
print('B', time, previous_value, measure_value, forecast)
#cent = float(input("STOP"))
else:
# The measure didn't change and we have no previous forecast
forecast = (time +
old_div(float(self._measures[-1].active_node_count * time),
(self._measures[-1].node_count -
self._measures[-1].active_node_count)))
print('C', time, previous_value, measure_value, forecast)
self._forecasts.append(TimeForecast(time, forecast))
#if (time >= 200):
# cent = float(input("STOP"))
#if (forecast >= 60000):
# cent = float(input("STOP"))
def GetForecasts(self):
return self._forecasts
def GetMeasures(self):
return self._measures
class ForecastingChainedSequences(object):
"""
"""
def __init__(self):
self._sequences = []
self._scale_factors = []
self._current_sequence = -1
def AddMeasure(self, time, value, active_node_count, node_count):
assert self._current_sequence >= 0
self._sequences[self._current_sequence].AddMeasure(
ProgressMeasurement(time, value, active_node_count, node_count))
def StartNewSequence(self, scale_factor):
"""Starts a new sequence of measures.
The scale factor should compare only to the previous sequence.
"""
if self._scale_factors:
scale_factor *= self._scale_factors[-1]
self._scale_factors.append(scale_factor)
assert len(self._sequences) == self._current_sequence + 1
if self._current_sequence < 0:
self._sequences.append(DoubleExponentialSmoothingForecaster(
scale_factor, -1, -1))
else:
self._sequences.append(DoubleExponentialSmoothingForecaster(
scale_factor, self._sequences[self._current_sequence]._b_t,
self._sequences[self._current_sequence]._first_time))
self._current_sequence += 1
assert len(self._scale_factors) == len(self._sequences)
def GetAllForecasts(self):
forecasts = []
for sequence in self._sequences:
forecasts.extend(sequence.GetForecasts())
return forecasts
def GetAllMeasures(self):
measures = []
for i, sequence in enumerate(self._sequences):
temp_measures = sequence.GetMeasures()
for measure in temp_measures:
measure.value *= self._scale_factors[i]
measures.extend(temp_measures)
return measures
Classes
class DoubleExponentialSmoothingForecaster (scale_factor, first_value, first_time)
-
Uses double exponential smoothing to forecast values.
Expand source code
class DoubleExponentialSmoothingForecaster(object): """Uses double exponential smoothing to forecast values. """ def __init__(self, scale_factor, first_value, first_time): self._measures = [] self._forecasts = [] self._b_t = None self._S_t = None self._scale_factor = float(scale_factor) self._first_time = -1 if first_value is None: self._first_value = 1 else: self._first_value = float(first_value) self._alpha = 0.5 self._lambda = 0.5 self._gamma = 0.5 self._counter = 0 def AddMeasure(self, measurement): self._measures.append(measurement) self.ComputeForecast() def ComputeForecast(self): # Check if the forecast was already computed if len(self._forecasts) == len(self._measures) - 3: return # Must have at least 4 measurements to make a forecast. if len(self._measures) < 4: return # Check that a minimum amount of time has passed if self._measures[-1].time - self._measures[-2].time < 1: return measure_value = self._measures[-1].value * self._scale_factor previous_value = self._measures[-2].value * self._scale_factor time = self._measures[-1].time previous_time = self._measures[-2].time delta = old_div(float((measure_value - previous_value)), float((time - previous_time))) if -delta < 0.00001 * measure_value: delta = 0 # Set initial parameters if necessary otherwise update the estimators if self._b_t is None: if self._first_value > 0: self._b_t = delta self._S_t = (measure_value * self._alpha + previous_value * (1 - self._alpha)) else: self._b_t = self._first_value self._S_t = (measure_value * self._alpha + previous_value * (1 - self._alpha)) if self._b_t < -measure_value: self._b_t = -measure_value self._S_t = (measure_value * self._alpha + previous_value * (1 - self._alpha)) updated = False if delta < 0: self._b_t = self._gamma * delta + (1 - self._gamma) * self._b_t self._S_t = (self._alpha * measure_value + (1 - self._alpha) * self._S_t) updated = True print('delta: %f' %delta) print('self._lambda*self._b_t: %f' %(self._lambda*self._b_t)) if delta < self._lambda * self._b_t: if not updated: self._b_t = self._gamma * delta + (1 - self._gamma) * self._b_t self._S_t = (self._alpha * measure_value + (1 - self._alpha) * self._S_t) updated = True forecast = (time + (old_div(float(-self._S_t), min(delta,self._b_t)))) print('A', time, previous_value, measure_value, end=' ') print(self._S_t, self._b_t, forecast) #cent = float(input("STOP")) elif len(self._forecasts) >= 1: # The measure didn't change but we have a previous forecast if self._forecasts[-1].forecast >= time: forecast = self._forecasts[-1].forecast else: forecast = (time + (old_div(float(self._measures[-1].active_node_count), self._measures[-2].active_node_count)) * (self._forecasts[-1].forecast - self._forecasts[-1].time)) print('B', time, previous_value, measure_value, forecast) #cent = float(input("STOP")) else: # The measure didn't change and we have no previous forecast forecast = (time + old_div(float(self._measures[-1].active_node_count * time), (self._measures[-1].node_count - self._measures[-1].active_node_count))) print('C', time, previous_value, measure_value, forecast) self._forecasts.append(TimeForecast(time, forecast)) #if (time >= 200): # cent = float(input("STOP")) #if (forecast >= 60000): # cent = float(input("STOP")) def GetForecasts(self): return self._forecasts def GetMeasures(self): return self._measures
Methods
def AddMeasure(self, measurement)
-
Expand source code
def AddMeasure(self, measurement): self._measures.append(measurement) self.ComputeForecast()
def ComputeForecast(self)
-
Expand source code
def ComputeForecast(self): # Check if the forecast was already computed if len(self._forecasts) == len(self._measures) - 3: return # Must have at least 4 measurements to make a forecast. if len(self._measures) < 4: return # Check that a minimum amount of time has passed if self._measures[-1].time - self._measures[-2].time < 1: return measure_value = self._measures[-1].value * self._scale_factor previous_value = self._measures[-2].value * self._scale_factor time = self._measures[-1].time previous_time = self._measures[-2].time delta = old_div(float((measure_value - previous_value)), float((time - previous_time))) if -delta < 0.00001 * measure_value: delta = 0 # Set initial parameters if necessary otherwise update the estimators if self._b_t is None: if self._first_value > 0: self._b_t = delta self._S_t = (measure_value * self._alpha + previous_value * (1 - self._alpha)) else: self._b_t = self._first_value self._S_t = (measure_value * self._alpha + previous_value * (1 - self._alpha)) if self._b_t < -measure_value: self._b_t = -measure_value self._S_t = (measure_value * self._alpha + previous_value * (1 - self._alpha)) updated = False if delta < 0: self._b_t = self._gamma * delta + (1 - self._gamma) * self._b_t self._S_t = (self._alpha * measure_value + (1 - self._alpha) * self._S_t) updated = True print('delta: %f' %delta) print('self._lambda*self._b_t: %f' %(self._lambda*self._b_t)) if delta < self._lambda * self._b_t: if not updated: self._b_t = self._gamma * delta + (1 - self._gamma) * self._b_t self._S_t = (self._alpha * measure_value + (1 - self._alpha) * self._S_t) updated = True forecast = (time + (old_div(float(-self._S_t), min(delta,self._b_t)))) print('A', time, previous_value, measure_value, end=' ') print(self._S_t, self._b_t, forecast) #cent = float(input("STOP")) elif len(self._forecasts) >= 1: # The measure didn't change but we have a previous forecast if self._forecasts[-1].forecast >= time: forecast = self._forecasts[-1].forecast else: forecast = (time + (old_div(float(self._measures[-1].active_node_count), self._measures[-2].active_node_count)) * (self._forecasts[-1].forecast - self._forecasts[-1].time)) print('B', time, previous_value, measure_value, forecast) #cent = float(input("STOP")) else: # The measure didn't change and we have no previous forecast forecast = (time + old_div(float(self._measures[-1].active_node_count * time), (self._measures[-1].node_count - self._measures[-1].active_node_count))) print('C', time, previous_value, measure_value, forecast) self._forecasts.append(TimeForecast(time, forecast))
def GetForecasts(self)
-
Expand source code
def GetForecasts(self): return self._forecasts
def GetMeasures(self)
-
Expand source code
def GetMeasures(self): return self._measures
class ForecastingChainedSequences
-
Expand source code
class ForecastingChainedSequences(object): """ """ def __init__(self): self._sequences = [] self._scale_factors = [] self._current_sequence = -1 def AddMeasure(self, time, value, active_node_count, node_count): assert self._current_sequence >= 0 self._sequences[self._current_sequence].AddMeasure( ProgressMeasurement(time, value, active_node_count, node_count)) def StartNewSequence(self, scale_factor): """Starts a new sequence of measures. The scale factor should compare only to the previous sequence. """ if self._scale_factors: scale_factor *= self._scale_factors[-1] self._scale_factors.append(scale_factor) assert len(self._sequences) == self._current_sequence + 1 if self._current_sequence < 0: self._sequences.append(DoubleExponentialSmoothingForecaster( scale_factor, -1, -1)) else: self._sequences.append(DoubleExponentialSmoothingForecaster( scale_factor, self._sequences[self._current_sequence]._b_t, self._sequences[self._current_sequence]._first_time)) self._current_sequence += 1 assert len(self._scale_factors) == len(self._sequences) def GetAllForecasts(self): forecasts = [] for sequence in self._sequences: forecasts.extend(sequence.GetForecasts()) return forecasts def GetAllMeasures(self): measures = [] for i, sequence in enumerate(self._sequences): temp_measures = sequence.GetMeasures() for measure in temp_measures: measure.value *= self._scale_factors[i] measures.extend(temp_measures) return measures
Methods
def AddMeasure(self, time, value, active_node_count, node_count)
-
Expand source code
def AddMeasure(self, time, value, active_node_count, node_count): assert self._current_sequence >= 0 self._sequences[self._current_sequence].AddMeasure( ProgressMeasurement(time, value, active_node_count, node_count))
def GetAllForecasts(self)
-
Expand source code
def GetAllForecasts(self): forecasts = [] for sequence in self._sequences: forecasts.extend(sequence.GetForecasts()) return forecasts
def GetAllMeasures(self)
-
Expand source code
def GetAllMeasures(self): measures = [] for i, sequence in enumerate(self._sequences): temp_measures = sequence.GetMeasures() for measure in temp_measures: measure.value *= self._scale_factors[i] measures.extend(temp_measures) return measures
def StartNewSequence(self, scale_factor)
-
Starts a new sequence of measures.
The scale factor should compare only to the previous sequence.
Expand source code
def StartNewSequence(self, scale_factor): """Starts a new sequence of measures. The scale factor should compare only to the previous sequence. """ if self._scale_factors: scale_factor *= self._scale_factors[-1] self._scale_factors.append(scale_factor) assert len(self._sequences) == self._current_sequence + 1 if self._current_sequence < 0: self._sequences.append(DoubleExponentialSmoothingForecaster( scale_factor, -1, -1)) else: self._sequences.append(DoubleExponentialSmoothingForecaster( scale_factor, self._sequences[self._current_sequence]._b_t, self._sequences[self._current_sequence]._first_time)) self._current_sequence += 1 assert len(self._scale_factors) == len(self._sequences)
class ProgressMeasurement (time, value, active_node_count, node_count)
-
Key data recording progress. Data members are public.
Expand source code
class ProgressMeasurement(object): """Key data recording progress. Data members are public. """ def __init__(self, time, value, active_node_count, node_count): self.time = float(time) self.value = float(value) self.active_node_count = int(active_node_count) self.node_count = int(node_count)
class TimeForecast (time, forecast)
-
Time-stamped forecast of time remaining.
Expand source code
class TimeForecast(object): """Time-stamped forecast of time remaining. """ def __init__(self, time, forecast): self.time = float(time) self.forecast = float(forecast)