Skip to content

Commit

Permalink
release v0.1.5 - "ensemble tolerance"
Browse files Browse the repository at this point in the history
  • Loading branch information
Yann Büchau committed Jan 13, 2017
1 parent a67d766 commit 4b32182
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 234 deletions.
6 changes: 6 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
simbuto (0.1.5) unstable; urgency=medium

* implement ensemble tolerance forecast

-- Yann Büchau <[email protected]> Fri, 13 Jan 2017 22:48:00 +0100

simbuto (0.1.4) unstable; urgency=medium

* implement amount and day tolerance
Expand Down
2 changes: 1 addition & 1 deletion usr/lib/simbuto/python/simbuto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Internal modules

# the version
VERSION = "0.1.4"
VERSION = "0.1.5"

__version__ = VERSION

Expand Down
96 changes: 60 additions & 36 deletions usr/lib/simbuto/python/simbuto/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def __init__(self):
signals = [signal.SIGINT, signal.SIGTERM, signal.SIGHUP],
handler = self.quit
)
# can't use Gtk.main() because of a bug that prevents proper SIGINT
# handling. use Glib.MainLoop() directly instead.
self.mainloop = GLib.MainLoop() # main loop


##################
Expand Down Expand Up @@ -74,6 +77,10 @@ def currently_edited_file(self):
except AttributeError:
return None

@property
def is_running(self):
return self.mainloop.is_running()

@currently_edited_file.setter
def currently_edited_file(self, value):
self._currently_edited_file = value
Expand Down Expand Up @@ -405,6 +412,15 @@ def setup_gui(self):
# statusbar
self.reset_statusbar() # initially reset statusbar

# settings
self("ensemble_expander_label").set_text(_("Statistics"))
self("ensemble_settings_useensemble_checkbutton").set_label(_(
"display ensemble"))
self("ensemble_settings_useensemble_checkbutton").set_tooltip_text(_(
"[slower] Based on the given day and amount tolerances, run an "
"ensemble and display the 10% and 90% quantiles as darker shadow."))


# calendar
# pretend the start date was selected and let automatic range selection
# do the rest
Expand Down Expand Up @@ -465,40 +481,51 @@ def update_statusbar(self, text = None):
statuslabel.set_text(newtext)

def update_graph_from_editor(self, *args, size=None):
# rect = self("plot_image").get_allocation()
if size is None: # use current size
rect = self("plot_scrolledwindow").get_allocation()
width = rect.width
height = rect.height
else: # use given size
width, height = size

try:
currentfile = os.path.basename(self.currently_edited_file)
except AttributeError:
currentfile = _("unnamed-budget")
name = "{}.png".format(currentfile)
filename = os.path.join(config.personal_simbuto_dotfolder(),
"plots",name)
self.update_statusbar(_("updating graph..."))
success = self.signalmanager.emit_signal("create-graph-from-text",
filename=filename, # to this file
text = self.current_editor_content, # this text
width = width, height = height, # these dimensions
start = self.selected_start_date, # this start date
end = self.selected_end_date, # this end date
)
if success[0]:
self.logger.debug(_("The graph file was obviously "
"sucessfully updated."))
self.update_graph_from_file(filename)
self.update_statusbar(_("Graph updated"))
if self.is_running: # only if gui is running
# rect = self("plot_image").get_allocation()
if size is None: # use current size
rect = self("plot_scrolledwindow").get_allocation()
width = rect.width
height = rect.height
else: # use given size
width, height = size

try:
currentfile = os.path.basename(self.currently_edited_file)
except AttributeError:
currentfile = _("unnamed-budget")

cb = self("ensemble_settings_useensemble_checkbutton")
use_ensemble = cb.get_active()

name = "{}.png".format(currentfile)
filename = os.path.join(config.personal_simbuto_dotfolder(),
"plots",name)
self.update_statusbar(_("updating graph..."))
success = self.signalmanager.emit_signal("create-graph-from-text",
filename=filename, # to this file
text = self.current_editor_content, # this text
width = width, height = height, # these dimensions
start = self.selected_start_date, # this start date
end = self.selected_end_date, # this end date
use_ensemble = use_ensemble, # use the ensemble or not
)
if success[0]:
self.logger.debug(_("The graph file was obviously "
"sucessfully updated."))
self.update_graph_from_file(filename)
self.update_statusbar(_("Graph updated"))
else:
self.logger.debug(_("There was a problem updating the graph."))
self.update_statusbar(_("[WARNING] There was a problem "
"updating the graph. Please check the input!"))
return True
else:
self.logger.debug(_("There was a problem updating the graph."))
self.update_statusbar(_("[WARNING] There was a problem "
"updating the graph. Please check the input!"))

return True
self.updating_graph_from_editor_is_now_okay = True
self.logger.debug(_("The gui is not running. Refusing to update "
"the graph now! Remembering this for gui start."))
return False


def update_graph_from_file(self, filename):
self("plot_image").set_from_file(filename)
Expand Down Expand Up @@ -747,9 +774,6 @@ def fill_editor_from_file(self, filename):

# run the gui
def run(self):
# can't use Gtk.main() because of a bug that prevents proper SIGINT
# handling. use Glib.MainLoop() directly instead.
self.mainloop = GLib.MainLoop() # main loop
# signal.signal(signal.SIGINT, signal.SIG_DFL)
self.logger.debug(_("Starting GLib main loop..."))
self.mainloop.run()
Expand Down
12 changes: 10 additions & 2 deletions usr/lib/simbuto/python/simbuto/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ def md5sum_of_file(self, filename):
def create_png_graph_from_text(self, text, filename,
width = 600, height = 400,
start = datetime.datetime.now(),
end = datetime.datetime.now() + datetime.timedelta(365) ):
end = datetime.datetime.now() + datetime.timedelta(365),
ensemble_size = 100,
use_ensemble = False):
""" Create a png graph from simbuto csv-like text
Args:
text (str): the csv-like simbuto budget
Expand All @@ -117,21 +119,27 @@ def create_png_graph_from_text(self, text, filename,
end [Optional(datetime.datetime)]: the end time of the budget
calculation and plotting. Defaults to the current day plus one
year.
use_ensemble [Optional(bool)]: calculat an ensemble? Defaults to
False.
ensemble_size [Optional(int)]: The ensemble size to use. Defaults to
100.
Returns:
success (bool): True if graph png file was created, False otherwise
"""
start_date = R("as.Date('{}-{}-{}')".format(
start.year,start.month,start.day))
end_date = R("as.Date('{}-{}-{}')".format(
end.year,end.month,end.day))
if not use_ensemble:
ensemble_size = R("NULL")
try:
# append newline
if not text.endswith("\n"): text += "\n"
# create the budget from text
budget_frame = R.read_budget_from_text(text = text)
# create the timeseries from the budget
timeseries_frame = R.timeseries_from_budget(budget = budget_frame,
start = start_date, end = end_date)
start = start_date, end = end_date, ensemble_size=ensemble_size)
# plot to png
R.plot_budget_timeseries_to_png(filename=filename,
timeseries = timeseries_frame, width = width, height = height)
Expand Down
119 changes: 75 additions & 44 deletions usr/lib/simbuto/r/simbuto-functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ timeseries_from_budget <- function(
# create empty frame with day series
all.days <- seq.Date(from = start, to = end, by = "days")
MONEY <- data.frame(day = all.days, amount = 0)
N <- nrow(MONEY)

# start with empty series
worstcase <- bestcase <- undisturbed <- rep(0, nrow(MONEY))
worstcase <- bestcase <- undisturbed <- rep(0, N)
# loop over all facts
for (factnr in 1:nrow(budget)) {
fact <- budget[factnr,] # current fact
Expand All @@ -39,7 +40,7 @@ timeseries_from_budget <- function(
fact.end <- fact.start
interval = "day" # pick any interval, doesn't matter
}
# cat("from=",fact.start," to=",fact.end," by=",interval," length.out=",number.occurences,"\n")
# print(fact)
occurences <- c()
if(fact.start <= fact.end) {
occurences <- seq.Date(from = fact.start, to = fact.end, by = interval)
Expand All @@ -49,20 +50,54 @@ timeseries_from_budget <- function(

# get the indices
indices <- na.omit(match(x = occurences, table = MONEY$day))

# create the series
# undisturbed - original
undisturbed <- undisturbed + fact_amounts_series(
occurences = occurences_bool, fact = fact,with_tolerance = FALSE)
# worst case
worstcase <- worstcase + fact_amounts_series(
occurences = occurences_bool, fact = fact,with_tolerance = TRUE,
worst_case = TRUE )
# best case
bestcase <- bestcase + fact_amounts_series(
occurences = occurences_bool, fact = fact,with_tolerance = TRUE,
worst_case = FALSE )
# ensemble
if(any(is.finite(ensemble_size))) {
if(!exists("ensemble")) # create variable if not existant yet
ensemble <- matrix(0,nrow=ensemble_size, ncol = N)
# do the runs
# cat("create members...")
for(i in 1:ensemble_size) {
tendencies <- fact_amounts_series(
occurences = occurences_bool, fact = fact,with_tolerance = TRUE,
random_tolerance = TRUE)
# add to ensemble run
ensemble[i,] <- ensemble[i,] + tendencies
}
}
}

# cumulate
MONEY$amount = cumsum(undisturbed)
MONEY$worstcase = cumsum(worstcase)
MONEY$bestcase = cumsum(bestcase)
# ensemble
if(exists("ensemble")) {
# cumulate
ensemble <- t(apply(X = ensemble, MARGIN = 1, FUN = cumsum))
# MONEY$ensmean <- apply(X = ensemble,MARGIN = 2, FUN = mean)
# MONEY$ensmedian <- apply(X = ensemble,MARGIN = 2, FUN = median)
# MONEY$ensquant25 <- apply(X = ensemble,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.25)))
# MONEY$ensquant75 <- apply(X = ensemble,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.75)))
# MONEY$ensquant10 <- apply(X = ensemble,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.10)))
# MONEY$ensquant90 <- apply(X = ensemble,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.90)))
MONEY$ensquant05 <- apply(X = ensemble,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.05)))
MONEY$ensquant95 <- apply(X = ensemble,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.95)))
# MONEY$ensmin <- apply(X = ensemble,MARGIN = 2, FUN = min)
# MONEY$ensmax <- apply(X = ensemble,MARGIN = 2, FUN = max)
}
# empty data frame
return(MONEY)
}
Expand Down Expand Up @@ -98,15 +133,15 @@ fact_amounts_series <- function(
if(with_tolerance) {
if(random_tolerance) {
# modify amount randomly
amounts <- runif( n = length(indices),
amounts <- round(runif( n = length(indices),
min = fact_amount - fact_tolerance_amount,
max = fact_amount + fact_tolerance_amount
)
))
# modify indices randomly
indices <- indices + runif( n = length(indices),
min = - fact_tolerance_amount,
max = + fact_tolerance_amount
)
indices <- indices + round(runif( n = length(indices),
min = - fact_tolerance_day,
max = + fact_tolerance_day
))
} else {
if(worst_case) {
# worst case: all costs are highest
Expand All @@ -123,6 +158,7 @@ fact_amounts_series <- function(
# best case: all incomes are earliest
indices <- indices - sign(fact_amount) * fact_tolerance_day
}
}
# fix indices that lie outside the output vector
# cat("indices before fixing: ",indices,"\n")
# cat("amounts before fixing: ",amounts,"\n")
Expand All @@ -140,13 +176,16 @@ fact_amounts_series <- function(
}
# cat("indices after fixing: ",indices,"\n")
# cat("amounts after fixing: ",amounts,"\n")
}
} else {
# keep amount
amounts <- rep(fact_amount, length(indices))
}
# cat("amounts before putting into out: ",amounts,"\n")

# cat("indices: ",indices,"\n")
# cat("amounts: ",amounts,"\n")
stopifnot(all(1<=indices|indices<=N))
stopifnot(length(indices) == length(amounts))
# set the amounts to the indices
out[indices] <- amounts
# cat("out: ",out,"\n")
Expand All @@ -155,39 +194,6 @@ fact_amounts_series <- function(
return(out)
}

budget_ensemble<- function( budget,
start = Sys.Date(), end = Sys.Date() + 365,
ensemble_size = 100
) {
# run without tolerance
timeseries_without_tolerance <- timeseries_from_budget(
budget = budget, start = start, end = end, with_tolerance = TRUE,
random_tolerance = FALSE)
# the ensemble out starts with the bare run
ENSEMBLE_OUT <- timeseries_without_tolerance
if(any(is.finite(budget$tolerance_amount))) {
# create ensemble matrix
ENSEMBLE <- matrix(NA,nrow=ensemble_size, ncol = nrow(ENSEMBLE_OUT))
# do the runs
# cat("create members...")
for(i in 1:ensemble_size) {
ENSEMBLE[i,] <- timeseries_from_budget( budget = budget,
start = start, end = end,
with_tolerance = TRUE, random_tolerance=TRUE)$amount
}
cat("done!\n")
# calculate statistics
# cat("calculate statistics...")
ENSEMBLE_OUT$ensmean <- apply(X = ENSEMBLE,MARGIN = 2, FUN = mean)
ENSEMBLE_OUT$ensmedian <- apply(X = ENSEMBLE,MARGIN = 2, FUN = median)
ENSEMBLE_OUT$ensquant25 <- apply(X = ENSEMBLE,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.25)))
ENSEMBLE_OUT$ensquant75 <- apply(X = ENSEMBLE,MARGIN = 2, FUN = function(x)quantile(x,probs = c(0.75)))
ENSEMBLE_OUT$ensmin <- apply(X = ENSEMBLE,MARGIN = 2, FUN = min)
ENSEMBLE_OUT$ensmax <- apply(X = ENSEMBLE,MARGIN = 2, FUN = max)
# cat("done!\n")
}
return(ENSEMBLE_OUT)
}

plot_budget_timeseries <- function(timeseries) {
plotrange <- range(c(timeseries$amount,timeseries$worstcase,
Expand Down Expand Up @@ -223,7 +229,32 @@ plot_budget_timeseries <- function(timeseries) {
if(!is.null(timeseries$worstcase) & !is.null(timeseries$bestcase)) {
polygon(x = c(timeseries$day,rev(timeseries$day)),
y = c(timeseries$worstcase,rev(timeseries$bestcase)),
col = "#00000022",border=NA)
col = "#00000033",border=NA)
}
if(!is.null(timeseries$ensmin) & !is.null(timeseries$ensmax)) {
polygon(x = c(timeseries$day,rev(timeseries$day)),
y = c(timeseries$ensmin,rev(timeseries$ensmax)),
col = "#00000033",border=NA)
}
if(!is.null(timeseries$ensquant05) & !is.null(timeseries$ensquant95)) {
polygon(x = c(timeseries$day,rev(timeseries$day)),
y = c(timeseries$ensquant05,rev(timeseries$ensquant95)),
col = "#00000033",border=NA)
}
if(!is.null(timeseries$ensquant10) & !is.null(timeseries$ensquant90)) {
polygon(x = c(timeseries$day,rev(timeseries$day)),
y = c(timeseries$ensquant10,rev(timeseries$ensquant90)),
col = "#00000033",border=NA)
}
if(!is.null(timeseries$ensquant25) & !is.null(timeseries$ensquant75)) {
polygon(x = c(timeseries$day,rev(timeseries$day)),
y = c(timeseries$ensquant25,rev(timeseries$ensquant75)),
col = "#00000033",border=NA)
}
if(!is.null(timeseries$ensmean)) {
lines(x = timeseries$day, y = timeseries$ensmean
,lwd = 2, lty = 2
)
}
# raw run
lines(x = timeseries$day, y = timeseries$amount
Expand All @@ -240,7 +271,7 @@ plot_budget_timeseries_to_png <- function(timeseries,filename,width=600,height=4

#### read data ####
# BUDGET <- read_budget_from_text(readLines("~/Downloads/budget.simbuto"))
# MONEY <- timeseries_from_budget(budget = BUDGET)
# MONEY <- timeseries_from_budget(budget = BUDGET, ensemble_size = 500)
# cat("plotting...")
# plot_budget_timeseries(MONEY)
# cat("done!\n")
Loading

0 comments on commit 4b32182

Please sign in to comment.